/// <summary> /// Convert everything and save it to the directory! Also write out /// all the sub-directories. /// </summary> /// <param name="calculateEverything">If true, then trigger all calculations here and below. Normally call with false.</param> public void Write(bool calculateEverything = true) { // Write everything associated with this directory. using (ROOTLock.Lock()) { Directory.Write(); } // Trigger all the calculatiosn that are needed for these directories. // This drives the ability for parallel calculation of everything. if (calculateEverything) { TriggerResolutions().Wait(); } // Local values foreach (var item in _heldValues) { item.Save(Directory); } _heldValues.Clear(); // Next, the subdirectories if (_subDirs.IsValueCreated) { foreach (var item in _subDirs.Value) { item.Write(); } } }
/// <summary> /// Called after running to load the results. /// </summary> /// <returns></returns> private async Task <IDictionary <string, NTObject> > LoadSelectorResults(FileInfo queryResultsFile) { using (var holder = await ROOTLock.LockAsync()) { if (!queryResultsFile.Exists) { throw new FileNotFoundException($"Unable to find the file {queryResultsFile.FullName} - it should contain the results of the query."); } // Read the data from the file. var results = new Dictionary <string, ROOTNET.Interface.NTObject>(); var f = ROOTNET.NTFile.Open(queryResultsFile.FullName); try { var list = f.Get("output") as NTList; foreach (var o in list) { results[o.Name] = o; } } finally { f.Close(); } return(results); } }
/// <summary> /// Create a TH1F plot from a stream of objects (with a lambda function to give flexability in conversion). /// </summary> /// <typeparam name="TSource">The type of the sequence that the plot will be run over</typeparam> /// <param name="source">The sequence over which a plot should be made. There will be one entry per item in the sequence.</param> /// <param name="plotName">The histogram will be created with this name</param> /// <param name="plotTitle">The histogram will be created with this title</param> /// <param name="nbins">Number of bins this histogram should have</param> /// <param name="lowBin">The xmin value for this histogram</param> /// <param name="highBin">The xmax value for this histogram</param> /// <param name="xValue">A lambda that returns the xvalue for each sequence item.</param> /// <param name="weight">A lambda that returns the weight for each sequence item. By default every entry has a weight of 1.</param> /// <returns></returns> public static ROOTNET.NTH1F Plot <TSource> ( this IQueryable <TSource> source, string plotName, string plotTitle, int nbins, double lowBin, double highBin, Expression <Func <TSource, double> > xValue, Expression <Func <TSource, double> > weight = null) { using (ROOTLock.Lock()) { if (weight == null) { Expression <Func <TSource, double> > constWeight = s => 1.0; weight = constWeight; } var hParameter = Expression.Parameter(typeof(ROOTNET.NTH1F), "h"); var vParameter = Expression.Parameter(typeof(TSource), "v"); // h.Fill(getter(v), weight(v)) is what we want to code up var callGetter = Expression.Invoke(xValue, vParameter); var callWeight = Expression.Invoke(weight, vParameter); var fillMethod = typeof(ROOTNET.NTH1F).GetMethod("Fill", new[] { typeof(double), typeof(double) }); var callFill = Expression.Call(hParameter, fillMethod, callGetter, callWeight); var lambda = Expression.Lambda <Action <ROOTNET.NTH1F, TSource> >(callFill, hParameter, vParameter); var h = new ROOTNET.NTH1F(plotName, plotTitle.ReplaceLatexStrings(), nbins, lowBin, highBin); ConfigureHisto(h); return(source.ApplyToObject(h, lambda)); } }
async Task GenerateValue() { using (await ROOTLock.LockAsync()) { _histo = new ROOTNET.NTH1F("hi", "there", 10, 0.0, 100.0); _histo.Fill(5.0); } }
/// <summary> /// A clone of an int is an int. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="o"></param> /// <returns></returns> public async Task <T> Clone <T>(T o) { var h = o as ROOTNET.Interface.NTH1; using (await ROOTLock.LockAsync()) { return((T)h.Clone()); } }
/// <summary> /// Put all variables we have to move over to the running root in a file so it can be open and read. /// </summary> /// <param name="cmds"></param> /// <param name="queryResultsFile"></param> /// <param name="varsToTransfer"></param> private async Task WriteInputVariablesForTransfer(StringBuilder cmds, FileInfo queryResultsFile, IEnumerable <KeyValuePair <string, object> > varsToTransfer, DirectoryInfo queryDirectory) { // Objects that are headed over need to be stored in a file and then loaded into the selector. if (varsToTransfer != null && varsToTransfer.Count() > 0) { TraceHelpers.TraceInfo(20, "RunNtupleQuery: Saving the objects we are going to ship over"); var inputFilesFilename = new FileInfo(Path.Combine(queryResultsFile.DirectoryName, "TSelectorInputFiles.root")); // Next, move through and actually write everything out. using (await ROOTLock.LockAsync()) { Debug.WriteLine($"Aquired ROOTLOCK WriteInputVariablesForTransfer"); // Clone the objects. Do it outside of writing so we can make sure that we do not // accidentally clone them or assign them in the wrong place (we are in a multithreaded environment). ROOTNET.NTH1.AddDirectory(false); ROOTNET.NTROOT.gROOT.cd(); var clonedObjects = varsToTransfer .Select(i => (v: i.Value as ROOTNET.Interface.NTObject, k: i.Key)) .Select(i => i.v.Clone(i.k)) .ToArray(); // Now, write them out to a file, and code up the stuff that reads them and puts them into the TSelector input list. var outgoingVariables = ROOTNET.NTFile.Open(inputFilesFilename.FullName, "RECREATE"); if (!outgoingVariables.IsOpen()) { Trace.WriteLine($"Unable to open {inputFilesFilename.FullName} for writing TSelector input variables!"); throw new UnableToWriteOutVariablesForQueryException($"Unable to open {inputFilesFilename.FullName} for writing TSelector input variables!"); } // Write out the code to load them and stash them remotely if need be. // It is important to do this after the file is created, or the assumption logic in NormalizeFileForTarget will fail. try { var safeInputFilename = await NormalizeFileForTarget(inputFilesFilename, queryDirectory); cmds.AppendLine($"TFile *varsInFile = TFile::Open(\"{safeInputFilename}\", \"READ\");"); cmds.AppendLine("selector->SetInputList(new TList());"); // Write out the files now foreach (var item in clonedObjects) { outgoingVariables.WriteTObject(item); cmds.AppendLine($"selector->GetInputList()->Add(varsInFile->Get(\"{item.Name}\"));"); item.SetNull(); // Make sure we aren't tracking this any more. } } finally { outgoingVariables.Close(); } } } }
private static async Task <ROOTNET.Interface.NTFile> CreateOpenFile(string name) { var oldDir = ROOTNET.NTDirectory.CurrentDirectory(); using (await ROOTLock.LockAsync()) { try { var f = ROOTNET.NTFile.Open(name, "RECREATE"); if (!f.IsOpen()) { throw new InvalidOperationException(string.Format("Unable to create file '{0}'. It could be the file is locked by another process (like ROOT!!??)", name)); } return(f); } finally { oldDir.cd(); } } }
/// <summary> /// Ok, back on the client with the result. We need to try and load the thing in. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="iVariable"></param> /// <param name="obj"></param> /// <returns></returns> /// <remarks> /// WARNING: this must be protected by the ROOT lock! /// </remarks> public async Task <T> LoadResult <T>(IDeclaredParameter iVariable, RunInfo[] obj) { if (obj == null) { throw new ArgumentNullException("Obj cannot be null"); } var named = obj[0]._result as ROOTNET.Interface.NTNamed; if (named == null) { throw new ArgumentException("Object isn't named"); } if (iVariable == null) { throw new ArgumentNullException("Variable can't be null"); } var rootObjInfo = iVariable.InitialValue as TypeHandlerROOT.ROOTObjectStaticHolder; if (rootObjInfo == null) { throw new InvalidOperationException("iVariable must be a ROOTObjectCopiedValue!"); } using (await ROOTLock.LockAsync()) { ROOTNET.NTH1.AddDirectory(false); var result = named.Clone() as ROOTNET.Interface.NTNamed; // Restore name and title - which might be different since our cache is blind // to those things. result.Name = rootObjInfo.OriginalName; result.Title = rootObjInfo.OriginalTitle; return((T)result); } }
/// <summary> /// Write out an object. Eventually, with ROOTNET improvements this will work better and perahps /// won't be needed! /// </summary> /// <param name="obj">The object to be written. Assumed not null.</param> /// <param name="dir"></param> internal static void InternalWriteObject(this ROOTNET.Interface.NTObject obj, ROOTNET.Interface.NTDirectory dir) { if (obj == null) { Console.WriteLine("WARNING: Unable to write out null object to a TDirectory!"); return; } using (ROOTLock.Lock()) { if (obj is ROOTNET.Interface.NTH1 h) { var copy = h.Clone(); dir.WriteTObject(copy); // Ugly from a memory pov, but... copy.SetNull(); } else { dir.WriteTObject(obj); obj.SetNull(); } } }
/// <summary> /// Create a canvas that is a set of stacked plots. /// </summary> /// <remarks> /// Only TH1F plots are dealt with properly here. Everything else is ignored and no stacked plot will be emitted. /// </remarks> /// <param name="histos"></param> /// <param name="canvasName">Name given to the canvas</param> /// <param name="canvasTitle">Title that will be put at the top of the canvas</param> /// <param name="colorize">True if colors should be automattically assigned to the canvas.</param> /// <param name="logy">True if the y axis should be log scale</param> /// <param name="normalize">True if the histograms should be set to normal area (1) before being plotted</param> /// <param name="legendContainsOnlyUniqueTitleWords">If true, then common words in the histogram titles are removed before they are used for the legend</param> /// <returns></returns> public static ROOTNET.Interface.NTCanvas PlotStacked(this ROOTNET.Interface.NTH1[] histos, string canvasName, string canvasTitle, bool logy = false, bool normalize = false, bool legendContainsOnlyUniqueTitleWords = true, bool colorize = true) { if (histos == null || histos.Length == 0) { return(null); } // Always build a clone... because that way if the histogram is modified after we look at it, the plot will be what // the user intended. using (ROOTLock.Lock()) { var hToPlot = (from h in histos where (h as ROOTNET.Interface.NTH1) != null select h.Clone(string.Format("{0}{1}", h.Name, canvasName)) as ROOTNET.Interface.NTH1).ToArray(); if (hToPlot.Length == 0) { var msg = new StringBuilder(); msg.Append("Warning: Only able to build a stacked plot for TH1F type plots ("); foreach (var p in histos) { msg.AppendFormat(" {0}", p.Name); } msg.Append(")"); Console.WriteLine(msg.ToString()); return(null); } foreach (var h in hToPlot) { h.SetDirectory(null); } // // If we have to normalize first, we need to normalize first! // if (normalize) { hToPlot = (from h in hToPlot select h.Normalize()).ToArray(); } // // Reset the colors on these guys // if (colorize) { var cloop = new ColorLoop(); foreach (var h in hToPlot) { h.LineColor = cloop.NextColor(); } } // // Remove common words from the titles. // if (legendContainsOnlyUniqueTitleWords && hToPlot.Length > 1) { var splitTitles = from h in hToPlot select h.Title.Split(); var wordList = from index in Enumerable.Range(0, splitTitles.Select(ar => ar.Count()).Max()) select(from titleWords in splitTitles select titleWords.Skip(index).FirstOrDefault()).ToArray(); var isTheSame = (from wl in wordList select(wl.All(tword => tword == wl.First()))).ToArray(); var fixedTitleStrings = from twords in splitTitles select( from h in twords.Zip(isTheSame, (tword, issame) => issame ? "" : tword) where !string.IsNullOrWhiteSpace(h) select h ); foreach (var histAndTitle in hToPlot.Zip(fixedTitleStrings, (h, strArr) => Tuple.Create(h, strArr))) { string title = string.Join(" ", histAndTitle.Item2); histAndTitle.Item1.Title = title; } } // // Grab the x and y axis titles from the first histogram // var xaxisTitle = hToPlot[0].Xaxis.Title; var yaxisTitle = hToPlot[0].Yaxis.Title; // // Use the nice ROOT utility THStack to make the plot. Once we do this, the plot is now owned by the TCanvas. // var stack = new ROOTNET.NTHStack(canvasName + "Stack", canvasTitle.ReplaceLatexStrings()); foreach (var h in hToPlot) { stack.Add(h); h.SetNull(); } // // Now do the plotting. Use the THStack to get all the axis stuff correct. // If we are plotting a log plot, then make sure to set that first before // calling it as it will use that information during its painting. // var result = new ROOTNET.NTCanvas(canvasName, canvasTitle.ReplaceLatexStrings()) { FillColor = ROOTNET.NTStyle.gStyle.FrameFillColor // This is not a sticky setting! }; if (logy) { result.Logy = 1; } stack.Draw("nostack"); if (!string.IsNullOrWhiteSpace(xaxisTitle)) { stack.Xaxis.Title = xaxisTitle; } if (!string.IsNullOrWhiteSpace(yaxisTitle)) { stack.Yaxis.Title = yaxisTitle; } stack.Draw("nostack"); // // The stack is now "attached" to the canvas. This means the canvas now owns it. So we // definately don't want the GC to delete it - so here we need to turn off the delete. // stack.SetNull(); // // And a legend! // result.BuildLegend(); // // Return the canvas so it can be saved to the file (or whatever). // return(result); } }
/// <summary> /// The detailed code that runs the query. /// </summary> /// <param name="tSelectorClassName">Name of the TSelector object</param> /// <param name="outputFileInfo">Where the output results should be written for eventual reading</param> private async Task <Dictionary <string, ROOTNET.Interface.NTObject> > RunNtupleQuery(string tSelectorClassName, IEnumerable <KeyValuePair <string, object> > variablesToLoad, string treeName, FileInfo[] rootFiles) { /// /// Create a new TSelector to run /// TraceHelpers.TraceInfo(18, "RunNtupleQuery: Startup - doing selector lookup"); var cls = ROOTNET.NTClass.GetClass(tSelectorClassName); if (cls == null) { throw new InvalidOperationException("Unable find class '" + tSelectorClassName + "' in the ROOT TClass registry that was just successfully compiled - can't run ntuple query - major inconsistency"); } var selector = cls.New() as ROOTNET.Interface.NTSelector; /// /// Create the chain and load file files into it. /// TraceHelpers.TraceInfo(19, "RunNtupleQuery: Creating the TChain"); using (var tree = new ROOTNET.NTChain(treeName)) { foreach (var f in rootFiles) { tree.Add(f.FullName); } // If there are any objects we need to send to the selector, then send them on now TraceHelpers.TraceInfo(20, "RunNtupleQuery: Saving the objects we are going to ship over"); var objInputList = new ROOTNET.NTList(); selector.InputList = objInputList; using (await ROOTLock.LockAsync()) { ROOTNET.NTH1.AddDirectory(false); foreach (var item in variablesToLoad) { var obj = item.Value as ROOTNET.Interface.NTNamed; if (obj == null) { throw new InvalidOperationException("Can only deal with named objects"); } var cloned = obj.Clone(item.Key); objInputList.Add(cloned); } } // Setup the cache for more efficient reading. We assume we are on a machine with plenty of memory // for this. tree.CacheSize = 1024 * 1024 * 100; // 100 MB cache if (LeafNames == null) { tree.AddBranchToCache("*", true); } else { foreach (var leaf in LeafNames) { tree.AddBranchToCache(leaf, true); } } tree.StopCacheLearningPhase(); // Always Do the async prefetching (this is off by default for some reason, but...). ROOTNET.Globals.gEnv.Value.SetValue("TFile.AsynchPrefetching", 1); // Finally, run the whole thing TraceHelpers.TraceInfo(21, "RunNtupleQuery: Running TSelector"); if (Environment.BreakToDebugger) { System.Diagnostics.Debugger.Break(); } tree.Process(selector); TraceHelpers.TraceInfo(22, "RunNtupleQuery: Done"); // If debug, dump some stats... if (Environment.CompileDebug) { tree.PrintCacheStats(); } // // Get the results and put them into a map for safe keeping! // Also, since we want the results to live beyond this guy, make sure that when // the selector is deleted the objects don't go away! // var results = new Dictionary <string, ROOTNET.Interface.NTObject>(); foreach (var o in selector.OutputList) { results[o.Name] = o; } selector.OutputList.SetOwner(false); return(results); } }