{ "cells": [ { "cell_type": "markdown", "id": "b6dab10a", "metadata": {}, "source": [ "# rf614_binned_fit_problems\n", "A tutorial that explains you how to solve problems with binning effects and\n", "numerical stability in binned fits.\n", "\n", "### Introduction\n", "\n", "In this tutorial, you will learn three new things:\n", "\n", " 1. How to reduce the bias in binned fits by changing the definition of the\n", " normalization integral\n", "\n", " 2. How to completely get rid of binning effects by integrating the pdf over each bin\n", "\n", " 3. How to improve the numeric stability of fits with a greatly different\n", " number of events per bin, using a constant per-bin counterterm\n", "\n", "\n", "\n", "\n", "**Author:** Jonas Rembser \n", "This notebook tutorial was automatically generated with ROOTBOOK-izer from the macro found in the ROOT repository on Tuesday, May 19, 2026 at 08:33 PM." ] }, { "cell_type": "markdown", "id": "63a54207", "metadata": {}, "source": [ " Generate binned Asimov dataset for a continuous pdf.\n", "One should in principle be able to use\n", "pdf.generateBinned(x, nEvents, RooFit::ExpectedData()).\n", "Unfortunately it has a problem: it also has the bin bias that this tutorial\n", "demonstrates, to if we would use it, the biases would cancel out.\n", " " ] }, { "cell_type": "code", "execution_count": 1, "id": "762bdc19", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:49.835039Z", "iopub.status.busy": "2026-05-19T20:33:49.834925Z", "iopub.status.idle": "2026-05-19T20:33:49.875031Z", "shell.execute_reply": "2026-05-19T20:33:49.874441Z" } }, "outputs": [], "source": [ "%%cpp -d\n", "std::unique_ptr generateBinnedAsimov(RooAbsPdf const &pdf, RooRealVar &x, int nEvents)\n", "{\n", " auto dataH = std::make_unique(\"dataH\", \"dataH\", RooArgSet{x});\n", " RooAbsBinning &xBinning = x.getBinning();\n", " for (int iBin = 0; iBin < x.numBins(); ++iBin) {\n", " x.setRange(\"bin\", xBinning.binLow(iBin), xBinning.binHigh(iBin));\n", " std::unique_ptr integ{pdf.createIntegral(x, RooFit::NormSet(x), RooFit::Range(\"bin\"))};\n", " integ->getVal();\n", " dataH->set(iBin, nEvents * integ->getVal(), -1);\n", " }\n", " return dataH;\n", "}" ] }, { "cell_type": "markdown", "id": "8a5217bb", "metadata": {}, "source": [ " Force numeric integration and do this numeric integration with the\n", "RooBinIntegrator, which sums the function values at the bin centers.\n", " " ] }, { "cell_type": "code", "execution_count": 2, "id": "22933551", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:49.876803Z", "iopub.status.busy": "2026-05-19T20:33:49.876680Z", "iopub.status.idle": "2026-05-19T20:33:49.881801Z", "shell.execute_reply": "2026-05-19T20:33:49.881217Z" } }, "outputs": [], "source": [ "%%cpp -d\n", "void enableBinIntegrator(RooAbsReal &func, int numBins)\n", "{\n", " RooNumIntConfig customConfig(*func.getIntegratorConfig());\n", " customConfig.method1D().setLabel(\"RooBinIntegrator\");\n", " customConfig.getConfigSection(\"RooBinIntegrator\").setRealValue(\"numBins\", numBins);\n", " func.setIntegratorConfig(customConfig);\n", " func.forceNumInt(true);\n", "}" ] }, { "cell_type": "markdown", "id": "0428a799", "metadata": {}, "source": [ " Reset the integrator config to disable the RooBinIntegrator.\n", " " ] }, { "cell_type": "code", "execution_count": 3, "id": "a3b096c3", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:49.883150Z", "iopub.status.busy": "2026-05-19T20:33:49.883029Z", "iopub.status.idle": "2026-05-19T20:33:49.885513Z", "shell.execute_reply": "2026-05-19T20:33:49.885027Z" } }, "outputs": [], "source": [ "%%cpp -d\n", "void disableBinIntegrator(RooAbsReal &func)\n", "{\n", " func.setIntegratorConfig();\n", " func.forceNumInt(false);\n", "}" ] }, { "cell_type": "code", "execution_count": 4, "id": "81faf73f", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:49.886973Z", "iopub.status.busy": "2026-05-19T20:33:49.886859Z", "iopub.status.idle": "2026-05-19T20:33:50.211677Z", "shell.execute_reply": "2026-05-19T20:33:50.210997Z" } }, "outputs": [], "source": [ "using namespace RooFit;" ] }, { "cell_type": "markdown", "id": "54b406d3", "metadata": {}, "source": [ "Silence info output for this tutorial" ] }, { "cell_type": "code", "execution_count": 5, "id": "5b17dd53", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:50.213817Z", "iopub.status.busy": "2026-05-19T20:33:50.213693Z", "iopub.status.idle": "2026-05-19T20:33:50.422898Z", "shell.execute_reply": "2026-05-19T20:33:50.422113Z" } }, "outputs": [], "source": [ "RooMsgService::instance().getStream(1).removeTopic(Minimization);\n", "RooMsgService::instance().getStream(1).removeTopic(Fitting);\n", "RooMsgService::instance().getStream(1).removeTopic(Generation);" ] }, { "cell_type": "markdown", "id": "c2f8b437", "metadata": {}, "source": [ "Exponential example\n", "-------------------" ] }, { "cell_type": "markdown", "id": "9eab9aa8", "metadata": {}, "source": [ "Set up the observable" ] }, { "cell_type": "code", "execution_count": 6, "id": "5056fa11", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:50.425090Z", "iopub.status.busy": "2026-05-19T20:33:50.424965Z", "iopub.status.idle": "2026-05-19T20:33:50.660789Z", "shell.execute_reply": "2026-05-19T20:33:50.643336Z" } }, "outputs": [], "source": [ "RooRealVar x{\"x\", \"x\", 0.1, 5.1};\n", "x.setBins(10); // fewer bins so we have larger binning effects for this demo" ] }, { "cell_type": "markdown", "id": "63219ae2", "metadata": {}, "source": [ "Let's first look at the example of an exponential function" ] }, { "cell_type": "code", "execution_count": 7, "id": "eb00ed09", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:50.668335Z", "iopub.status.busy": "2026-05-19T20:33:50.668208Z", "iopub.status.idle": "2026-05-19T20:33:50.877161Z", "shell.execute_reply": "2026-05-19T20:33:50.876413Z" } }, "outputs": [], "source": [ "RooRealVar c{\"c\", \"c\", -1.8, -5, 5};\n", "RooExponential expo{\"expo\", \"expo\", x, c};" ] }, { "cell_type": "markdown", "id": "fec1b303", "metadata": {}, "source": [ "Generate an Asimov dataset such that the only difference between the fit\n", "result and the true parameters comes from binning effects." ] }, { "cell_type": "code", "execution_count": 8, "id": "92099051", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:50.879103Z", "iopub.status.busy": "2026-05-19T20:33:50.878988Z", "iopub.status.idle": "2026-05-19T20:33:51.088695Z", "shell.execute_reply": "2026-05-19T20:33:51.087955Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[#1] INFO:Eval -- RooRealVar::setRange(x) new range named 'bin' created with bounds [0.1,0.6]\n" ] } ], "source": [ "std::unique_ptr expoData{generateBinnedAsimov(expo, x, 10000)};" ] }, { "cell_type": "markdown", "id": "7b95368f", "metadata": {}, "source": [ "If you do the fit the usual was in RooFit, you will get a bias in the\n", "result. This is because the continuous, normalized pdf is evaluated only\n", "at the bin centers." ] }, { "cell_type": "code", "execution_count": 9, "id": "66cb5834", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:51.090087Z", "iopub.status.busy": "2026-05-19T20:33:51.089965Z", "iopub.status.idle": "2026-05-19T20:33:51.299666Z", "shell.execute_reply": "2026-05-19T20:33:51.298985Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " RooFitResult: minimized FCN value: 4754.37, estimated distance to minimum: 3.09852e-09\n", " covariance matrix quality: Full, accurate covariance matrix\n", " Status : MINIMIZE=0 HESSE=0 \n", "\n", " Floating Parameter FinalValue +/- Error \n", " -------------------- --------------------------\n", " c -1.6862e+00 +/- 1.70e-02\n", "\n" ] } ], "source": [ "std::unique_ptr fit1{expo.fitTo(*expoData, Save(), PrintLevel(-1), SumW2Error(false))};\n", "fit1->Print();" ] }, { "cell_type": "markdown", "id": "d6113e5c", "metadata": {}, "source": [ "In the case of an exponential function, the bias that you get by\n", "evaluating the pdf only at the bin centers is a constant scale factor in\n", "each bin. Here, we can do a trick to get rid of the bias: we also\n", "evaluate the normalization integral for the pdf the same way, i.e.,\n", "summing the values of the unnormalized pdf at the bin centers. Like this\n", "the bias cancels out. You can achieve this by customizing the way how the\n", "pdf is integrated (see also the rf901_numintconfig tutorial)." ] }, { "cell_type": "code", "execution_count": 10, "id": "73063854", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:51.301062Z", "iopub.status.busy": "2026-05-19T20:33:51.300928Z", "iopub.status.idle": "2026-05-19T20:33:51.511754Z", "shell.execute_reply": "2026-05-19T20:33:51.511063Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[#0] WARNING:Integration -- RooBinIntegrator::RooBinIntegrator WARNING: integrand provide no binning definition observable #0 substituting default binning of 10 bins\n", "[#1] INFO:NumericIntegration -- RooRealIntegral::init(expo_Int[x]) using numeric integrator RooBinIntegrator to calculate Int(x)\n", "\n", " RooFitResult: minimized FCN value: 4440.6, estimated distance to minimum: 5.599e-07\n", " covariance matrix quality: Full, accurate covariance matrix\n", " Status : MINIMIZE=0 HESSE=0 \n", "\n", " Floating Parameter FinalValue +/- Error \n", " -------------------- --------------------------\n", " c -1.8000e+00 +/- 1.87e-02\n", "\n" ] } ], "source": [ "enableBinIntegrator(expo, x.numBins());\n", "std::unique_ptr fit2{expo.fitTo(*expoData, Save(), PrintLevel(-1), SumW2Error(false))};\n", "fit2->Print();\n", "disableBinIntegrator(expo);" ] }, { "cell_type": "markdown", "id": "854d1369", "metadata": {}, "source": [ "Power law example\n", "-----------------" ] }, { "cell_type": "markdown", "id": "123fb031", "metadata": {}, "source": [ "Let's not look at another example: a power law $$x^a$$." ] }, { "cell_type": "code", "execution_count": 11, "id": "0838ef4e", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:51.513126Z", "iopub.status.busy": "2026-05-19T20:33:51.512996Z", "iopub.status.idle": "2026-05-19T20:33:51.721904Z", "shell.execute_reply": "2026-05-19T20:33:51.720964Z" } }, "outputs": [], "source": [ "RooRealVar a{\"a\", \"a\", -0.3, -5.0, 5.0};\n", "RooPowerSum powerlaw{\"powerlaw\", \"powerlaw\", x, RooConst(1.0), a};\n", "std::unique_ptr powerlawData{generateBinnedAsimov(powerlaw, x, 10000)};" ] }, { "cell_type": "markdown", "id": "de55f8d4", "metadata": {}, "source": [ "Again, if you do a vanilla fit, you'll get a bias" ] }, { "cell_type": "code", "execution_count": 12, "id": "7238ab13", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:51.723341Z", "iopub.status.busy": "2026-05-19T20:33:51.723221Z", "iopub.status.idle": "2026-05-19T20:33:51.933160Z", "shell.execute_reply": "2026-05-19T20:33:51.932430Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " RooFitResult: minimized FCN value: 15816.4, estimated distance to minimum: 4.97037e-07\n", " covariance matrix quality: Full, accurate covariance matrix\n", " Status : MINIMIZE=0 HESSE=0 \n", "\n", " Floating Parameter FinalValue +/- Error \n", " -------------------- --------------------------\n", " a -2.6106e-01 +/- 1.06e-02\n", "\n" ] } ], "source": [ "std::unique_ptr fit3{powerlaw.fitTo(*powerlawData, Save(), PrintLevel(-1), SumW2Error(false))};\n", "fit3->Print();" ] }, { "cell_type": "markdown", "id": "076d87c7", "metadata": {}, "source": [ "This time, the bias is not the same factor in each bin! This means our\n", "trick by sampling the integral in the same way doesn't cancel out the\n", "bias completely. The average bias is canceled, but there are per-bin\n", "biases that remain. Still, this method has some value: it is cheaper than\n", "rigurously correcting the bias by integrating the pdf in each bin. So if\n", "you know your per-bin bias variations are small or performance is an\n", "issue, this approach can be sufficient." ] }, { "cell_type": "code", "execution_count": 13, "id": "1e4b496d", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:51.934524Z", "iopub.status.busy": "2026-05-19T20:33:51.934404Z", "iopub.status.idle": "2026-05-19T20:33:52.144459Z", "shell.execute_reply": "2026-05-19T20:33:52.143808Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[#0] WARNING:Integration -- RooBinIntegrator::RooBinIntegrator WARNING: integrand provide no binning definition observable #0 substituting default binning of 10 bins\n", "[#1] INFO:NumericIntegration -- RooRealIntegral::init(powerlaw_Int[x]) using numeric integrator RooBinIntegrator to calculate Int(x)\n", "\n", " RooFitResult: minimized FCN value: 15739.9, estimated distance to minimum: 4.99474e-07\n", " covariance matrix quality: Full, accurate covariance matrix\n", " Status : MINIMIZE=0 HESSE=0 \n", "\n", " Floating Parameter FinalValue +/- Error \n", " -------------------- --------------------------\n", " a -3.1481e-01 +/- 1.15e-02\n", "\n" ] } ], "source": [ "enableBinIntegrator(powerlaw, x.numBins());\n", "std::unique_ptr fit4{powerlaw.fitTo(*powerlawData, Save(), PrintLevel(-1), SumW2Error(false))};\n", "fit4->Print();\n", "disableBinIntegrator(powerlaw);" ] }, { "cell_type": "markdown", "id": "1e051dec", "metadata": {}, "source": [ "To get rid of the binning effects in the general case, one can use the\n", "IntegrateBins() command argument. Now, the pdf is not evaluated at the\n", "bin centers, but numerically integrated over each bin and divided by the\n", "bin width. The parameter for IntegrateBins() is the required precision\n", "for the numeric integrals. This is computationally expensive, but the\n", "bias is now not a problem anymore." ] }, { "cell_type": "code", "execution_count": 14, "id": "763454d7", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:52.145789Z", "iopub.status.busy": "2026-05-19T20:33:52.145663Z", "iopub.status.idle": "2026-05-19T20:33:52.354854Z", "shell.execute_reply": "2026-05-19T20:33:52.354116Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " RooFitResult: minimized FCN value: 15739.6, estimated distance to minimum: 3.92419e-05\n", " covariance matrix quality: Full, accurate covariance matrix\n", " Status : MINIMIZE=0 HESSE=0 \n", "\n", " Floating Parameter FinalValue +/- Error \n", " -------------------- --------------------------\n", " a -3.0010e-01 +/- 1.07e-02\n", "\n" ] } ], "source": [ "std::unique_ptr fit5{\n", " powerlaw.fitTo(*powerlawData, IntegrateBins(1e-3), Save(), PrintLevel(-1), SumW2Error(false))};\n", "fit5->Print();" ] }, { "cell_type": "markdown", "id": "a40d4dfd", "metadata": {}, "source": [ "Improving numerical stability\n", "-----------------------------" ] }, { "cell_type": "markdown", "id": "9ab7a226", "metadata": {}, "source": [ "There is one more problem with binned fits that is related to the binning\n", "effects because often, a binned fit is affected by both problems.\n", "\n", "The issue is numerical stability for fits with a greatly different number\n", "of events in each bin. For each bin, you have a term $$n\\log(p)$$ in\n", "the NLL, where $$n$$ is the number of observations in the bin, and\n", "$$p$$ the predicted probability to have an event in that bin. The\n", "difference in the logarithms for each bin is small, but the difference in\n", "$$n$$ can be orders of magnitudes! Therefore, when summing these terms,\n", "lots of numerical precision is lost for the bins with less events." ] }, { "cell_type": "markdown", "id": "9aa27724", "metadata": {}, "source": [ "We can study this with the example of an exponential plus a Gaussian. The\n", "Gaussian is only a faint signal in the tail of the exponential where\n", "there are not so many events. And we can't afford any precision loss for\n", "these bins, otherwise we can't fit the Gaussian." ] }, { "cell_type": "code", "execution_count": 15, "id": "db6c30db", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:52.356253Z", "iopub.status.busy": "2026-05-19T20:33:52.356134Z", "iopub.status.idle": "2026-05-19T20:33:52.565915Z", "shell.execute_reply": "2026-05-19T20:33:52.565263Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[#0] PROGRESS:Generation -- RooAbsPdf::generateBinned(model) Performing costly accept/reject sampling. If this takes too long, use extended mode to speed up the process.\n" ] } ], "source": [ "x.setBins(100); // It's not about binning effects anymore, so reset the number of bins.\n", "\n", "RooRealVar mu{\"mu\", \"mu\", 3.0, 0.1, 5.1};\n", "RooRealVar sigma{\"sigma\", \"sigma\", 0.5, 0.01, 5.0};\n", "RooGaussian gauss{\"gauss\", \"gauss\", x, mu, sigma};\n", "\n", "RooRealVar nsig{\"nsig\", \"nsig\", 10000, 0, 1e9};\n", "RooRealVar nbkg{\"nbkg\", \"nbkg\", 10000000, 0, 1e9};\n", "RooRealVar frac{\"frac\", \"frac\", nsig.getVal() / (nsig.getVal() + nbkg.getVal()), 0.0, 1.0};\n", "\n", "RooAddPdf model{\"model\", \"model\", {gauss, expo}, {nsig, nbkg}};\n", "\n", "std::unique_ptr modelData{model.generateBinned(x)};" ] }, { "cell_type": "markdown", "id": "6091ef83", "metadata": {}, "source": [ "Set the starting values for the Gaussian parameters away from the true\n", "value such that the fit is not trivial." ] }, { "cell_type": "code", "execution_count": 16, "id": "174d64ce", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:52.567202Z", "iopub.status.busy": "2026-05-19T20:33:52.567084Z", "iopub.status.idle": "2026-05-19T20:33:52.776853Z", "shell.execute_reply": "2026-05-19T20:33:52.776190Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " RooFitResult: minimized FCN value: -1.47174e+08, estimated distance to minimum: 0.162057\n", " covariance matrix quality: Full, accurate covariance matrix\n", " Status : MINIMIZE=-1 HESSE=3 \n", "\n", " Floating Parameter FinalValue +/- Error \n", " -------------------- --------------------------\n", " c -1.7972e+00 +/- 7.39e-04\n", " mu 2.9756e+00 +/- 3.90e-02\n", " nbkg 1.0001e+07 +/- 3.25e+03\n", " nsig 9.4264e+03 +/- 7.36e+02\n", " sigma 4.6849e-01 +/- 2.75e-02\n", "\n" ] } ], "source": [ "mu.setVal(2.0);\n", "sigma.setVal(1.0);\n", "\n", "std::unique_ptr fit6{model.fitTo(*modelData, Save(), PrintLevel(-1), SumW2Error(false))};\n", "fit6->Print();" ] }, { "cell_type": "markdown", "id": "e340fdf0", "metadata": {}, "source": [ "You should see in the previous fit result that the fit did not converge:\n", "the `MINIMIZE` return code should be -1 (a successful fit has status code zero)." ] }, { "cell_type": "markdown", "id": "edbbd4ae", "metadata": {}, "source": [ "To improve the situation, we can apply a numeric trick: if we subtract in\n", "each bin a constant counterterm $$n\\log(n/N)$$, we get terms for each\n", "bin that are closer to each other in order of magnitude as long as the\n", "initial model is not extremely off. Proving this mathematically is left\n", "as an exercise to the reader." ] }, { "cell_type": "markdown", "id": "8d23d40a", "metadata": {}, "source": [ "This counterterms can be enabled by passing the Offset(\"bin\") option to\n", "RooAbsPdf::fitTo() or RooAbsPdf::createNLL()." ] }, { "cell_type": "code", "execution_count": 17, "id": "c18521ff", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:52.778163Z", "iopub.status.busy": "2026-05-19T20:33:52.778046Z", "iopub.status.idle": "2026-05-19T20:33:52.987791Z", "shell.execute_reply": "2026-05-19T20:33:52.987131Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " RooFitResult: minimized FCN value: 3416.14, estimated distance to minimum: 0.000238299\n", " covariance matrix quality: Full, accurate covariance matrix\n", " Status : MINIMIZE=0 HESSE=0 \n", "\n", " Floating Parameter FinalValue +/- Error \n", " -------------------- --------------------------\n", " c -1.7971e+00 +/- 7.26e-04\n", " mu 2.9939e+00 +/- 3.63e-02\n", " nbkg 1.0001e+07 +/- 3.24e+03\n", " nsig 9.2425e+03 +/- 6.92e+02\n", " sigma 4.5747e-01 +/- 2.59e-02\n", "\n" ] } ], "source": [ "std::unique_ptr fit7{\n", " model.fitTo(*modelData, Offset(\"bin\"), Save(), PrintLevel(-1), SumW2Error(false))};\n", "fit7->Print();" ] }, { "cell_type": "markdown", "id": "1168ef8e", "metadata": {}, "source": [ "You should now see in the last fit result that the fit has converged." ] }, { "cell_type": "markdown", "id": "37cca4f1", "metadata": {}, "source": [ "Draw all canvases " ] }, { "cell_type": "code", "execution_count": 18, "id": "0a79fc99", "metadata": { "collapsed": false, "execution": { "iopub.execute_input": "2026-05-19T20:33:52.989118Z", "iopub.status.busy": "2026-05-19T20:33:52.989000Z", "iopub.status.idle": "2026-05-19T20:33:53.245796Z", "shell.execute_reply": "2026-05-19T20:33:53.231359Z" } }, "outputs": [], "source": [ "%jsroot on\n", "gROOT->GetListOfCanvases()->Draw()" ] } ], "metadata": { "kernelspec": { "display_name": "ROOT C++", "language": "c++", "name": "root" }, "language_info": { "codemirror_mode": "text/x-c++src", "file_extension": ".C", "mimetype": " text/x-c++src", "name": "c++" } }, "nbformat": 4, "nbformat_minor": 5 }