{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"This notebook explains what I called \"optimal linear weight\" in the [1st place solution](https://www.kaggle.com/competitions/g2net-detecting-continuous-gravitational-waves/discussion/375910).\n\nWeighted sum is common in data analysis and a typical example is the chi-squared fitting,\n$$\\chi^2(\\theta) =\n\\sum_n \\left( \\frac{x_n - x_{\\mathrm{model}, n}(\\theta)}{\\Delta x} \\right)^2, $$\nin which the weight is inversely proportional to the measurement error $\\Delta x$.\n\nIn this competition, the signal-to-noise ratio (S/N) is small and the signal is hard to distinguish from noise. We can improve S/N by multiplying the data by the weight proportional to the signal.\n\n\nThis notebook shows,\n- why the weight proportional to the signal is optimal;\n- how much the weights improve S/N; the improvement is 25% for frequency- and time-dependent weight, respectively, 50% in total.\n\nActual signal search using these weights is not included in this notebook (the notebook is already long enough).","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport math\nimport h5py\nimport scipy.stats","metadata":{"execution":{"iopub.status.busy":"2023-01-21T02:14:53.999277Z","iopub.execute_input":"2023-01-21T02:14:53.999731Z","iopub.status.idle":"2023-01-21T02:14:54.006056Z","shell.execute_reply.started":"2023-01-21T02:14:53.999696Z","shell.execute_reply":"2023-01-21T02:14:54.004688Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data\n\nLoad signal and noise generated with PyFstat.\n\n### Signal","metadata":{}},{"cell_type":"code","source":"i = 0\nodir = '/kaggle/input/g2net2-templates/sim5/depth20'\nfilename = '%s/%06d.h5' % (odir, i)\n\nwith h5py.File(filename, 'r') as f:\n    signal = f['signal'][:] * 1e22\n    noise = f['noise'][:] * 1e22\nnch, H, W = signal.shape\n\n# Load template frequency f(t)\nfilename = '%s/fit/fit%06d.h5' % (odir, i)\n    \n# Signal power\np = signal.real**2 + signal.imag**2    # 2 x H x W\nimax = np.argmax(p, axis=1)  # 2 x W\np_total = np.sum(p, axis=1)  # 2 x W\n\n# Image\nimg = np.mean(p.reshape(nch, H, 120, 48), axis=3)\n\nplt.figure(figsize=(6, 3))\nplt.title('Signal only data')\nplt.xlabel('time [day]')\nplt.ylabel('frquency bin')\nplt.imshow(np.sqrt(img[0]), cmap='Greys')\nplt.colorbar()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-21T02:14:54.014416Z","iopub.execute_input":"2023-01-21T02:14:54.014801Z","iopub.status.idle":"2023-01-21T02:14:55.118432Z","shell.execute_reply.started":"2023-01-21T02:14:54.01477Z","shell.execute_reply":"2023-01-21T02:14:55.117299Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The signal,\n$$ S(t) = A e^{i (\\omega t + \\phi)}, $$\nhas approximately constant amplitude $A$ and angular frequency $\\omega$ during the short-time Fourier transform and the Fourier coefficients are,\n\n$S_n = \\int_0^T S(t) e^{-i \\omega_n t} dt = Ae^{i \\phi} \\, \\frac{\\sin[ (\\omega - \\omega_n) T / 2]}{ (\\omega - \\omega_n) T / 2} \\,\n e^{i(\\omega - \\omega_n) T/2}$,\n \nwhere $\\omega_n = 2\\pi n / T$ is the angular frequency for the Fourier modes. The frequency dependence is sinc with an alternating sign, $e^{i \\omega_n T/2} = (-1)^n$.","metadata":{"execution":{"iopub.status.busy":"2023-01-11T11:41:10.423013Z","iopub.execute_input":"2023-01-11T11:41:10.42392Z","iopub.status.idle":"2023-01-11T11:41:10.433336Z","shell.execute_reply.started":"2023-01-11T11:41:10.423883Z","shell.execute_reply":"2023-01-11T11:41:10.431727Z"}}},{"cell_type":"code","source":"y = np.abs(signal[0, :, 0])**2 / p_total[0, 0]\nx = np.arange(len(y))\n\nxx = np.linspace(0, len(y), 2001)\ny_fit = np.sinc(xx - 46.63)**2\n\nplt.figure(figsize=(6, 3))\nplt.title('Signal distribution among frequency bins')\nplt.xlabel('$n$ [frequency bin]')\nplt.ylabel('Power')\nplt.xlim(42, 52)\nplt.plot(x, y, 'x', label='signal')\nplt.plot(xx, y_fit, label='$\\mathrm{sinc}^2$')\nplt.legend(frameon=False)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-01-21T02:14:55.120365Z","iopub.execute_input":"2023-01-21T02:14:55.120712Z","iopub.status.idle":"2023-01-21T02:14:55.342414Z","shell.execute_reply.started":"2023-01-21T02:14:55.12068Z","shell.execute_reply":"2023-01-21T02:14:55.341188Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The signal power $|S_n|^2$ is indeed $\\mathrm{sinc}^2$","metadata":{}},{"cell_type":"markdown","source":"### Noise\n\nThe noise is Gaussian, independent and identically distributed,\n\n$$\\langle \\delta_n \\rangle = 0$$\n\n$$\\langle \\delta_n \\delta_n^* \\rangle = \\sigma^2$$\n\nwhere $\\langle \\dots \\rangle$ is the statistical mean and * is the complex conjugate.\n\nThe real and imaginary parts are also independent and identical with variance $\\sigma^2/2$.\n\nFor simplicity, I focus on the stationary and frequency-independent noise in this notebook, $\\sigma^2 = 2.25$.","metadata":{}},{"cell_type":"code","source":"sigma2 = 2.25\nbins = np.linspace(-4, 4, 101)\nxx = 0.5 * (bins[1:] + bins[:-1])\nyy = scipy.stats.norm.pdf(xx, 0, math.sqrt(sigma2 / 2))\nplt.figure(figsize=(6, 3))\nplt.title('Gaussian random noise')\nplt.xlabel('Noise $\\delta$')\nplt.ylabel('PDF')\nplt.hist(noise.real.flatten(), bins, density=True, histtype='step', label='Re')\nplt.hist(noise.imag.flatten(), bins, density=True, histtype='step', label='Im')\nplt.plot(xx, yy, color='gray', alpha=0.8, label='Gaussian')\nplt.legend(frameon=False)\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-01-21T02:14:55.343871Z","iopub.execute_input":"2023-01-21T02:14:55.34474Z","iopub.status.idle":"2023-01-21T02:14:56.326324Z","shell.execute_reply.started":"2023-01-21T02:14:55.344705Z","shell.execute_reply":"2023-01-21T02:14:56.325367Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Optimal weight for the frequency dependence\n\n### Derivation\n\nI find weights $w_n$ that maximize the signal-to-noise ratio\nfor the weighted sum:\n\n$\\bar{S} = \\sum_n w_n S_n$,\n\n$\\bar{\\delta} = \\sum_n w_n \\delta_n$,\n\n$\\mathrm{SNR} = |\\bar{S}|^2/\\langle |\\bar{\\delta}|^2 \\rangle$.\n\nThe overall constant multiplies both the signal and noise with the same factor and does not mean anything. A convenient normalization is to set the $L^2$ norm to 1, which keeps the noise variance the same:\n\n$\\lVert w \\rVert^2 = \\sum_n |w_n|^2 = 1$,\n\n$\\langle \\bar{N} \\bar{N}^* \\rangle = \\sum_{mn} w_m w_n^* \\langle \\delta_m \\delta_n^* \\rangle =\n \\lVert w \\rVert^2 \\sigma^2$.\n \nThe weight with maximum absolute value is not unique due to the overall phase symmetry,\n$w_n \\mapsto w_n e^{i \\varphi}$, so I find one of the maxima on the real axis. That is, find the\nmaximum of,\n\n- $2 \\mathrm{Re} \\sum_n w_n S_n = \\sum_n w_n S_n + w_n^* S_n^*$,\n- under the constraint $\\lVert w_n \\rVert^2 = 1$.\n\nThis can be done by taking the derivative with the Lagrangian multiplier $\\lambda$:\n\n$ L = \\sum_n w_n S_n + w_n^* S_n^* + \\lambda \\left[ \\sum_n w_n w_n^* - 1 \\right] $.\n\n$\\frac{\\partial{L}}{\\partial w_n^*} = S_n^* + \\lambda w_n = 0 $\n\n$w_n \\propto S_n^*$\n\nThis shows that the weight proportional to the signal maximizes S/N.\n\nIf you are uncomfortable with $\\partial/\\partial w_n^*$ treating $w$ and $w^*$ independently, you can use real and imaginary part of $w_n$ and get the same result.","metadata":{}},{"cell_type":"markdown","source":"### The weight\n\nThis notebook so far has shown that the weight in the frequency direction\n\n$w_n \\propto S_n^* = (-1)^n \\mathrm{sinc}(f - n)$\n\nmaximizes S/N for a single timestep. $f$ is the frequency of the signal and the integer $n$ is that of the Fourier mode, both in the units of the frequency bin (fundamental frequency $\\Delta f = 1/T$). The sinc function is defined as\n\n$\\mathrm{sinc}(x) = \\sin(\\pi x) / (\\pi x)$\n\nfollowing `numpy.sinc`.\n\nThe weight collects all the signal power without increasing the noise power,\n\n$\\left|\\sum_n w_n S_n \\right| = \\sum_{n=-\\infty}^\\infty\nA\\, \\mathrm{sinc}(f - n)^2 = A$,\n\nwhen the frequency $f$ in the weight matches the signal frequency. In practice, the number of weights is finite and I use 8. This captures 95% of the signal, and slowly converge to 1 for infinite weights.","metadata":{}},{"cell_type":"markdown","source":"### Evaluation","metadata":{}},{"cell_type":"code","source":"# Single frequency bin\nbins = np.linspace(0.35, 1, 41)\nf = np.random.uniform(-0.5, 0.5, 2000)\nsignal1 = np.sinc(f)**2\nm1 = np.mean(signal1)\n\n# SignalWidth w=8 sinc kernel \nw = 8\nnrand = 2000\nf = np.random.uniform(w / 2 - 1, w / 2, nrand)\nf_bin = np.arange(w)\nsinc_w = np.sinc(f.reshape(nrand, 1) - f_bin.reshape(1, w))\nsignal_w = np.sum(sinc_w**2, axis=1)**2\n\nplt.figure(figsize=(6, 3))\nplt.title('Signal power in width w sinc kernel')\nplt.xlabel('signal power / total')\nplt.hist(signal1, bins, histtype='step', density=False, label='single (w=1)')\n\nplt.hist(signal_w, bins, histtype='step', density=False, label='w=%d' % w)\nplt.legend(loc=2, frameon=False)\nplt.show()\n\nmw = np.mean(signal_w)\nprint('Mean signal power in one frequency bin %.4f' % m1)\nprint('Mean signal power with w=%d: %.4f' % (w, mw))\nprint('Improvement w=8/w=1: %.4f' % (mw / m1))","metadata":{"execution":{"iopub.status.busy":"2023-01-21T02:14:56.32883Z","iopub.execute_input":"2023-01-21T02:14:56.329187Z","iopub.status.idle":"2023-01-21T02:14:56.487024Z","shell.execute_reply.started":"2023-01-21T02:14:56.329155Z","shell.execute_reply":"2023-01-21T02:14:56.485703Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The signal power in one frquency bin is 40% in worst case, when the signal is exactly in the middle of two frequency bins, 78% in average. The sinc kernel with 8 weights captures 95% in average.","metadata":{}},{"cell_type":"markdown","source":"# Weight for time direction\n\nThe weights multiplying the complex-valued data can be exteded to multiple timesteps, but that would require accurate match of the phase difference between timesteps. I use the sinc weights for each time separatly, and then sum the power in the time direction.\n\nSimilar to the frequency direction, the optimal linear weight for summing the power,\n\n$P_S = \\sum_t w_t S_t S_t^*$,\n\n$\\hat{P}_N = \\sum_t w_t \\delta_t \\delta_t^* $,\n\nis the weight proportional to the signal:\n\n$w_t \\propto \\left| S_t \\right|^2$.\n\nThe signal and noise can be from one frequency or the weighted sum of multiple frequency bins. The linear compination of Gaussian noise is still Gaussian.","metadata":{}},{"cell_type":"markdown","source":"\n### Derivation\n\nThe squares of the data, $h_t = S_t + \\delta_t$, are summed along various signal curves, $t$ is\nsummed over 2 channels (H1 and L1) and 5760 timesteps.\n\n$ \\mathrm{power\\_sum} = \\sum_t w_t \\left| h_t \\right|^2\n = \\sum_t w_t \\left| S_t + \\delta_t \\right|^2$\n \nThe mean and variance of the noise are,\n\n$\\langle \\hat{P}_N \\rangle = \\sigma^2 \\sum_t w_t $,\n\n$\\mathrm{Var}\\, \\hat{P}_N = \\sigma^4 \\sum_t w^2_t $.\n\nThe latter requires some familiarity with Gaussian random variables and Wick's theorem or Isserlis' theorem,\n\n$\\langle \\delta_m \\delta_m^* \\delta_n \\delta_n^* \\rangle\n - \\langle \\delta_m \\delta_m^* \\rangle \\langle \\delta_n \\delta_n^* \\rangle\n = \\langle \\delta_m \\delta_n^* \\rangle \\langle \\delta_m^* \\delta_n \\rangle$.\n \nI use the standardized sum as the estimator for the signal:\n\n$\\mathrm{SS} = \\frac{\\mathrm{power\\_sum} \\,-\\, \\langle \\hat{P}_N \\rangle}{\\mathrm{std} \\hat{P}_N} $\n\nThe standardized sum has (almost) the standard normal distribution for the noise,\nand the signal is added to the noise,\n\n$\\langle \\mathrm{SS} \\rangle = \\sum_t w_t \\left| S_t \\right|^2 \\big/ \\sqrt{\\sum_t w_t^2} \\sigma^2$\n\nMaximizing this weighted power maximizes the statistical significance.\n\nAgain, the $L^2$ normalization is convinient for deriving the optimal weight,\n\n$\\lVert w \\rVert^2 = \\sum_t w_t^2 = 1$,\n\nfinding the extrema with the Lagrangian multiplier,\n\n$L = \\sum_t w_t S^2_t + \\lambda \\left[ \\sum_t w_t^2 - 1 \\right]$,\n\n$\\frac{\\partial L}{\\partial w_t} = S^2_t + 2 \\lambda w_t = 0 \\quad \\Rightarrow w_t \\propto S^2_t$.\n\nThe optimal weight is proportional to the amplitude squared.","metadata":{}},{"cell_type":"markdown","source":"### Daily amplitude modulation\n\nThe amplitude squared is same as the total power,\n\n```\np_total = np.sum(np.abs(signal)**2, axis=1)\n```\n\nThe amplitude has daily modulation because the angles between the detctors and the signal change as the Earth rotates.","metadata":{}},{"cell_type":"code","source":"def load_templates(ibegin=0, iend=4000):\n    \"\"\"\n    Load template frequencies and amplitudes\n    \"\"\"\n    sim_dir = '/kaggle/input/g2net2-templates/template/template'\n    freq_fits = []\n    amps = []\n    for i in range(ibegin, iend):\n        k = 10 * (i // 10_000 + 1)  # 0:10_000 -> 10k\n        filename = '%s/%dk/fit/fit%06d.h5' % (sim_dir, k, i)\n        with h5py.File(filename, 'r') as f:\n            freq_fit = f['f'][:]  # 2 x 5760\n            freq_fits.append(freq_fit.reshape(1, 2, 5760))\n            amp = f['p_total'][:]\n            amps.append(amp.reshape(1, 2, 5760))\n    \n    freq_fits = np.concatenate(freq_fits, axis=0)  # (n_templates, 2, 5760)\n    amps = np.concatenate(amps, axis=0)\n    f_shifts = freq_fits - np.expand_dims(freq_fits[:, 0, 0], axis=(1, 2))\n\n    mem = len(f_shifts) * 2 * 5760 * 4 * 2\n    print('Load %d templates (%.2f Mbytes)' % (len(f_shifts), mem / 1000**2))\n\n    return f_shifts, amps\n\n_, amp2 = load_templates()\namp2.shape","metadata":{"execution":{"iopub.status.busy":"2023-01-21T02:14:56.488631Z","iopub.execute_input":"2023-01-21T02:14:56.489802Z","iopub.status.idle":"2023-01-21T02:15:25.575141Z","shell.execute_reply.started":"2023-01-21T02:14:56.489753Z","shell.execute_reply":"2023-01-21T02:15:25.574001Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"t = np.arange(48*3) / 48\n\nplt.figure(figsize=(6, 3))\nplt.xlabel('time [day]')\nplt.ylabel('$A^2$')\nplt.plot(t, amp2[0, 0, :(48*3)])\nfor i in range(4):\n    plt.axvline(i, color='gray', alpha=0.5)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-21T02:15:25.576768Z","iopub.execute_input":"2023-01-21T02:15:25.577357Z","iopub.status.idle":"2023-01-21T02:15:25.731077Z","shell.execute_reply.started":"2023-01-21T02:15:25.577322Z","shell.execute_reply":"2023-01-21T02:15:25.729813Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Evaluation\n\nThe improvement in S/N by using the weight in the time-direction sum.","metadata":{}},{"cell_type":"code","source":"n_templates = len(amp2)\n\n# Optimal, proportional to the amplitude squared\nw_opt = amp2 / np.sqrt(np.sum(amp2**2, axis=(1, 2)).reshape(-1, 1, 1))\n\n# No weight (constant weight) for comparison\nw_const = np.ones_like(amp2)\nw_const /= np.sqrt(np.sum(w_const**2, axis=(1, 2)).reshape(-1, 1, 1))\n\n# Power sum signal\np_sum_const = np.sum(amp2 * w_const, axis=(1, 2))\np_sum_opt = np.sum(amp2 * w_opt, axis=(1, 2))","metadata":{"execution":{"iopub.status.busy":"2023-01-21T02:15:25.732744Z","iopub.execute_input":"2023-01-21T02:15:25.733092Z","iopub.status.idle":"2023-01-21T02:15:26.249756Z","shell.execute_reply.started":"2023-01-21T02:15:25.73306Z","shell.execute_reply":"2023-01-21T02:15:26.248564Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(6, 5))\nplt.subplot(2, 1, 1)\nplt.title('Power sum of signal')\nbins = np.linspace(20, 300, 41)\nplt.hist(p_sum_const, bins, histtype='step', label='simple sum')\nplt.hist(p_sum_opt, bins, histtype='step', label='weighted sum')\nplt.legend(frameon=False)\n\n# Weighted / unweighted\nr = p_sum_opt / p_sum_const\nm = np.mean(r)\n\nbins = np.linspace(0.95, 1.8, 41)\n\nplt.subplot(2, 1, 2)\nplt.xlabel('weighted/unweighted')\nplt.hist(r, bins, alpha=0.5, density=True)\n\nplt.show()\n\nprint('Mean improvment %.4f in weighted/unweighted' % m)","metadata":{"execution":{"iopub.status.busy":"2023-01-21T02:15:26.251414Z","iopub.execute_input":"2023-01-21T02:15:26.25179Z","iopub.status.idle":"2023-01-21T02:15:26.591926Z","shell.execute_reply.started":"2023-01-21T02:15:26.251759Z","shell.execute_reply":"2023-01-21T02:15:26.590815Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Noise\n\nThis is the standardized sum for noise only,\n\n$\\mathrm{SS}_N = \\frac{\\hat{P}_N \\,-\\, \\langle \\hat{P}_N \\rangle}{\\mathrm{std} \\hat{P}_N}, $\n\nwhich is (almost) standard normal distribution, and the weight with $\\lVert w \\rVert = 1$ does not change the distribution.\n","metadata":{"execution":{"iopub.status.busy":"2023-01-11T23:59:27.350647Z","iopub.execute_input":"2023-01-11T23:59:27.351167Z","iopub.status.idle":"2023-01-11T23:59:27.356141Z","shell.execute_reply.started":"2023-01-11T23:59:27.351127Z","shell.execute_reply":"2023-01-11T23:59:27.355137Z"}}},{"cell_type":"code","source":"p_noise = np.abs(noise)**2\nsigma2 = 2.25\n\nnoise_sum_mean = sigma2 * np.sum(w_opt[0], axis=(0, 1))\nnoise_sum = np.sum(p_noise * w_opt[0].reshape(2, 1, 5760), axis=(0, 2))\nstandardized_noise_sum = (noise_sum - noise_sum_mean) / sigma2\n\n# Unweighted (const weight)\nnoise_sum_mean_const = sigma2 * np.sum(w_const[0], axis=(0, 1))\nnoise_sum_const = np.sum(p_noise * w_const[0].reshape(2, 1, 5760), axis=(0, 2))\nstandardized_noise_sum_const = (noise_sum_const - noise_sum_mean_const) / sigma2\n\nbins = np.linspace(-4, 4, 41)\nxx = 0.5 * (bins[1:] + bins[:-1])\nyy = scipy.stats.norm.pdf(xx)\nplt.figure(figsize=(6, 3))\nplt.title('Noise')\nplt.xlabel('standardized noise sum')\nplt.hist(standardized_noise_sum_const.flatten(), bins,\n         density=True, histtype='step', label='simple sum')\nplt.hist(standardized_noise_sum.flatten(), bins,\n         density=True, histtype='step', label='weighted sum')\nplt.plot(xx, yy, color='black')\nplt.legend(frameon=False)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-21T02:15:26.593551Z","iopub.execute_input":"2023-01-21T02:15:26.594317Z","iopub.status.idle":"2023-01-21T02:15:27.067811Z","shell.execute_reply.started":"2023-01-21T02:15:26.594269Z","shell.execute_reply":"2023-01-21T02:15:27.066051Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The noise distribution is the same for weighted/unweighted, while the signal is larger for the weighted sum.","metadata":{}},{"cell_type":"markdown","source":"# Summary\n\nIn summary, I have shown that weights proportional \nto the signal maximize the signal-to-noise ratio, \nand the improvement is about 22% and 25% in frequency and time direction, respectively, 50% in total. ","metadata":{"execution":{"iopub.status.busy":"2023-01-12T00:00:39.3296Z","iopub.execute_input":"2023-01-12T00:00:39.330028Z","iopub.status.idle":"2023-01-12T00:00:39.336922Z","shell.execute_reply.started":"2023-01-12T00:00:39.329992Z","shell.execute_reply":"2023-01-12T00:00:39.335683Z"}}},{"cell_type":"code","source":"1.25 * 1.22","metadata":{"execution":{"iopub.status.busy":"2023-01-21T02:15:27.071352Z","iopub.execute_input":"2023-01-21T02:15:27.072176Z","iopub.status.idle":"2023-01-21T02:15:27.079018Z","shell.execute_reply.started":"2023-01-21T02:15:27.072123Z","shell.execute_reply":"2023-01-21T02:15:27.077841Z"},"trusted":true},"execution_count":null,"outputs":[]}]}