From 78e937579c00fa96bb91dbc490074d7e89bcc4f3 Mon Sep 17 00:00:00 2001 From: Eric Kooistra <kooistra@astron.nl> Date: Thu, 28 Nov 2024 14:09:43 +0100 Subject: [PATCH] Try Ncoefs < Ndelays. --- .../lofar2/model/pfb_os/multirate_mixer.ipynb | 257 ++++++++++++------ 1 file changed, 173 insertions(+), 84 deletions(-) diff --git a/applications/lofar2/model/pfb_os/multirate_mixer.ipynb b/applications/lofar2/model/pfb_os/multirate_mixer.ipynb index 493dc8bdac..a38afe203e 100644 --- a/applications/lofar2/model/pfb_os/multirate_mixer.ipynb +++ b/applications/lofar2/model/pfb_os/multirate_mixer.ipynb @@ -100,6 +100,7 @@ "text": [ "Ntaps = 8\n", "Ndft = 16\n", + "Ndelays = 128\n", "Ncoefs = 128\n", "\n", "wgSub = 1.5\n", @@ -123,12 +124,17 @@ "else:\n", " Ntaps = 8 # number of taps per polyphase FIR filter\n", " Ndft = 16 # DFT size\n", + " #Ntaps = 4\n", + " #Ndft = 8\n", " #Ntaps = 12\n", " #Ndft = 192\n", - "Ncoefs = Ndft * Ntaps\n", - "#Ncoefs = Ncoefs - 1 # try odd length\n", + "Ndelays = Ndft * Ntaps\n", + "Ncoefs = Ndelays\n", + "#Ncoefs = Ndelays - 2 # try shorter length to verify impact of hPairGroupDelay\n", + "#Ncoefs = Ndelays - 1 # try odd length\n", "print('Ntaps =', Ntaps)\n", "print('Ndft =', Ndft)\n", + "print('Ndelays =', Ndelays)\n", "print('Ncoefs =', Ncoefs)\n", "\n", "# Waveform generator\n", @@ -282,6 +288,11 @@ " # are not symmetrical (so not exactly linear phase), which makes it possible to verify\n", " # whether the coefficients are applied in the correct order.\n", " hPrototype = read_coefficients_file('../pfb_bunton_annotated/cl.txt')\n", + " if Ncoefs < Ndelays:\n", + " dDiv = (Ndelays - Ncoefs) // 2\n", + " dRem = (Ndelays - Ncoefs) % 2\n", + " hPrototype = hPrototype[dDiv : -dDiv - dRem]\n", + " print(len(hPrototype))\n", " #hPrototype /= np.sum(hPrototype)\n", " print(is_symmetrical(hPrototype))\n", "else:\n", @@ -305,11 +316,7 @@ " fcutoff = fpass\n", " hPrototype = signal.firwin(Ncoefs, fcutoff, window='hann', fs=fs)\n", " verify_result(is_symmetrical(hPrototype))\n", - "print('DC gain = %.10f' % np.sum(hPrototype))\n", - "\n", - "# Impulse response for upconverter or synthesis\n", - "fPrototype = np.flip(hPrototype)\n", - "fPrototype = hPrototype" + "print('DC gain = %.10f' % np.sum(hPrototype))" ] }, { @@ -504,6 +511,8 @@ "source": [ "# FIR filter group delay\n", "w, gd = signal.group_delay((hPrototype, [1.0]), w=1024, fs=1)\n", + "#w, gd = signal.group_delay((np.convolve(hPrototype, hPrototype), [1.0]), w=1024, fs=1)\n", + "#w, gd = signal.group_delay((np.convolve(hPrototype, np.flip(hPrototype)), [1.0]), w=1024, fs=1)\n", "#plt.title('FIR filter group delay')\n", "plt.plot(w, gd)\n", "plt.ylabel('Group delay [samples]')\n", @@ -513,6 +522,20 @@ "print('FIR filter group delay = %f' % hGroupDelay)" ] }, + { + "cell_type": "code", + "execution_count": 15, + "id": "ea14000d", + "metadata": {}, + "outputs": [], + "source": [ + "# Select impulse response for downconverter / analysys and for upconverter / synthesis\n", + "aPrototype = hPrototype\n", + "sPrototype = hPrototype\n", + "sPrototype = np.flip(hPrototype)\n", + "#hPrototype" + ] + }, { "cell_type": "markdown", "id": "ce7e672e", @@ -523,7 +546,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "e74340e4", "metadata": {}, "outputs": [], @@ -543,7 +566,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "bd2add56", "metadata": {}, "outputs": [], @@ -568,7 +591,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "7106ad3f", "metadata": {}, "outputs": [ @@ -578,7 +601,7 @@ "(0.0, 10.0)" ] }, - "execution_count": 17, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" }, @@ -618,7 +641,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "da53b25e", "metadata": {}, "outputs": [], @@ -630,17 +653,17 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "9acf0ec2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "<matplotlib.legend.Legend at 0x7fcff66a6ca0>" + "<matplotlib.legend.Legend at 0x7f06500e35b0>" ] }, - "execution_count": 19, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" }, @@ -658,7 +681,7 @@ "source": [ "# x[n] --> LO --> LPF --> D --> y[mD, k] [HARRIS Fig 6.2] \n", "xLoData = xData * LO\n", - "yData = signal.lfilter(hPrototype, [1.0], xLoData) # = y[n, k], Eq. 6.1\n", + "yData = signal.lfilter(aPrototype, [1.0], xLoData) # = y[n, k], Eq. 6.1\n", "yDown = down(yData, Ndown) # = y[mD, k]\n", "\n", "plt.plot(n_sub, yData.real, 'g-')\n", @@ -686,7 +709,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "b8036250", "metadata": {}, "outputs": [ @@ -731,7 +754,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "0663df66", "metadata": {}, "outputs": [], @@ -747,7 +770,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "3a039428", "metadata": {}, "outputs": [ @@ -761,8 +784,8 @@ ], "source": [ "# x[n] --> BPF --> D --> LOdown --> y[m D, k] [HARRIS Fig 6.7]\n", - "hBpf = hPrototype * np.exp(1j * w_k * np.arange(Ncoefs))\n", - "yBpfData = signal.lfilter(hBpf, [1.0], xData)\n", + "aBpf = aPrototype * np.exp(1j * w_k * np.arange(Ncoefs))\n", + "yBpfData = signal.lfilter(aBpf, [1.0], xData)\n", "yBpfDown = down(yBpfData, Ndown)\n", "yBpfDownLo = yBpfDown * LOdown # = y[m D, k]\n", "\n", @@ -791,7 +814,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "327236c2", "metadata": {}, "outputs": [ @@ -806,7 +829,7 @@ "source": [ "print('Ros =', Ros)\n", "if Ndown == Ndft:\n", - " yMaxDownBpf = maximal_downsample_bpf(xData, Ndown, kLo, hPrototype)\n", + " yMaxDownBpf = maximal_downsample_bpf(xData, Ndown, kLo, aPrototype)\n", " yMaxDownBpfLo = yMaxDownBpf # = yMaxDownBpf * LOdown, because LOdown = 1 when Ndown == Ndft\n", "\n", " result = np.all(np.isclose(yDown, yMaxDownBpfLo))\n", @@ -828,7 +851,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "aefa8615", "metadata": {}, "outputs": [ @@ -851,7 +874,7 @@ } ], "source": [ - "yDownBpf = non_maximal_downsample_bpf(xData, Ndown, kLo, Ndft, hPrototype)\n", + "yDownBpf = non_maximal_downsample_bpf(xData, Ndown, kLo, Ndft, aPrototype)\n", "yDownBpfLo = yDownBpf # = yDownBpf * LOdown * LOshift, because LOdown = 1 when Ndown == Ndft\n", " # and LOshift phase shifts compensate for time shift due to Ndown < Ndft\n", "\n", @@ -872,7 +895,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "f814c9b9", "metadata": {}, "outputs": [ @@ -891,7 +914,7 @@ "print('wgSub = %.1f, SNR_WG_dB = %.1f dB, Nsim = %d [Tsub]' % (wgSub, SNR_WG_dB, Nsim))\n", "if verifyPhase: \n", " # The phaseMargin >> c_atol, because it depends on the stop band attenuation of the\n", - " # hPrototype LPF. This is because the LO downconverts the positive frequency band\n", + " # aPrototype LPF. This is because the LO downconverts the positive frequency band\n", " # of the WG cos() wave, so the negative frequency band will appear in the stop band.\n", " if SNR_WG_dB < 100: \n", " # Determine some appropriate phase margin dependent on SNR.\n", @@ -914,7 +937,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "id": "dd7a9503", "metadata": {}, "outputs": [], @@ -951,7 +974,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "id": "3cf6aa74", "metadata": {}, "outputs": [], @@ -965,7 +988,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "id": "373ab5bb", "metadata": {}, "outputs": [], @@ -976,14 +999,14 @@ "# yDown ycUp ycUpLpf\n", "# y[mD, k] --> U ------> LPF --------> LOp --> ycUpLpfLo\n", "ycUp = up(yDown, Nup) # insert Nup - 1 zeros\n", - "ycUpLpf = Nup * signal.lfilter(fPrototype, [1.0], ycUp) # interpolate by Nup\n", + "ycUpLpf = Nup * signal.lfilter(sPrototype, [1.0], ycUp) # interpolate by Nup\n", "ycUpLpfLo = ycUpLpf * LOp * LOadjust # upconvert to positive bin kLo\n", "yrUpLpfLo = ycUpLpfLo.real * nofSsb # = ycUpLpfLo + np.conj(ycUpLpfLo), add negative bin -kLo" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "id": "a5c60be5", "metadata": {}, "outputs": [ @@ -991,7 +1014,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.507261028720982\n" + "wgSub = 1.500000\n", + "kLo = 2\n", + "xLen = 129 input samples\n", + "yrSNR = 5.98613 [dB], single bin\n", + "yrAmpl = 0.507261\n" ] }, { @@ -1012,11 +1039,21 @@ "plt.plot(xDelayed, 'r.-')\n", "plt.plot(yrUpLpfLo[hPairGroupDelay:], 'b.-')\n", "plt.plot(xyDiff, 'g')\n", - "#plt.xlim([100, 250])\n", + "#plt.xlim([0, 250])\n", "\n", "if not wgModulation:\n", + " offset = Ncoefs\n", + " xLen = len(xDelayed[offset:])\n", + " if xLen < Ncoefs:\n", + " print('Too low Nsim for proper yrSNR calculation %d < %d' % (xLen, Ncoefs))\n", + " verify_result(False)\n", + " yrSNR = snr_db(xDelayed[offset:], xyDiff[offset:])\n", " yrAmpl = np.sqrt(np.mean(np.abs(yrUpLpfLo[Ncoefs:]**2)) * nofSsb)\n", - " print(yrAmpl)" + " print('wgSub = %f' % wgSub)\n", + " print('kLo = %d' % kLo)\n", + " print('xLen = %d input samples' % xLen)\n", + " print('yrSNR = %.5f [dB], single bin' % yrSNR)\n", + " print('yrAmpl = %f' % yrAmpl)" ] }, { @@ -1033,7 +1070,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "7049249e", "metadata": {}, "outputs": [ @@ -1067,7 +1104,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "id": "64cc34f3", "metadata": {}, "outputs": [], @@ -1083,7 +1120,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "id": "71a91beb", "metadata": {}, "outputs": [ @@ -1098,10 +1135,10 @@ "source": [ "# ycDownLo ycLoUp \n", "# y[mD, k] --> LOpDown ---------> U -------> BPF --> ycLoUpBpf\n", - "fBpf = fPrototype * np.exp(1j * w_k * np.arange(Ncoefs))\n", + "sBpf = sPrototype * np.exp(1j * w_k * np.arange(Ncoefs))\n", "ycDownLo = yDown * LOpDown * LOadjust # upconvert to positive bin kLo\n", "ycLoUp = Nup * up(ycDownLo, Nup) # insert Nup - 1 zeros\n", - "ycLoUpBpf = signal.lfilter(fBpf, [1.0], ycLoUp) # interpolate by Nup with BPF at kLo\n", + "ycLoUpBpf = signal.lfilter(sBpf, [1.0], ycLoUp) # interpolate by Nup with BPF at kLo\n", "yrLoUpBpf = ycLoUpBpf.real * nofSsb # = ycLoUpBpf + np.conj(ycLoUpBpf), add negative bin -kLo\n", "\n", "# result is True for any Ndft, Ndown, because LOdown is in equation of yBpfDownLo\n", @@ -1133,7 +1170,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "id": "a3aae48a", "metadata": {}, "outputs": [ @@ -1149,7 +1186,7 @@ "print('Ndft =', Ndft)\n", "if Nup == Ndft:\n", " # ycDownLo = yDown * LOdown = yDown, because LOdown = 1 when Ndown == Ndft\n", - " ycLoBpfMaxUp = maximal_upsample_bpf(yDown, Nup, kLo, hPrototype)\n", + " ycLoBpfMaxUp = maximal_upsample_bpf(yDown, Nup, kLo, sPrototype)\n", " yrLoBpfMaxUp = ycLoBpfMaxUp.real * nofSsb # add negative bin -kLo to make real\n", "\n", " result = np.all(np.isclose(yrUpLpfLo, yrLoBpfMaxUp))\n", @@ -1170,12 +1207,12 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "id": "3c2c8ec5", "metadata": {}, "outputs": [], "source": [ - "ycLoBpfUp = None #non_maximal_upsample_bpf(yDown, Nup, kLo, Ndft, hPrototype)\n", + "ycLoBpfUp = None #non_maximal_upsample_bpf(yDown, Nup, kLo, Ndft, sPrototype)\n", "if ycLoBpfUp is not None:\n", " yrLoBpfUp = ycLoBpfUp.real * nofSsb # add negative bin -kLo to make real\n", "\n", @@ -1197,6 +1234,30 @@ "Can use 'cw' or 'ccw' independently for analysis and synthesis PFB, because with fold() the IDFT can be expressed as a DFT and vice versa. However to have back to back DFT - IDFT in pipeline, use analysis 'cw' and synthesis 'ccw' [CROCHIERE 7.2.3]." ] }, + { + "cell_type": "code", + "execution_count": 36, + "id": "abb548f6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "wgSub = 1.500000\n", + "kLo = 2\n", + "Ndft = 16\n", + "Ndown = 12\n" + ] + } + ], + "source": [ + "print('wgSub = %f' % wgSub)\n", + "print('kLo = %d' % kLo)\n", + "print('Ndft = %d' % Ndft)\n", + "print('Ndown = %d' % Ndown)" + ] + }, { "cell_type": "markdown", "id": "d8bb436d", @@ -1207,33 +1268,51 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 37, + "id": "a6de2be1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PASSED\n", + "PASSED\n", + "PASSED\n" + ] + } + ], + "source": [ + "vb = 0\n", + "Ac0 = analysis_dft_filterbank(xData, Ndown, Ndft, aPrototype, 'wola', coefsOrder='bwin', verbosity=vb)\n", + "Ac1 = analysis_dft_filterbank(xData, Ndown, Ndft, aPrototype, 'wola', coefsOrder='bfir', verbosity=vb)\n", + "Ac2 = analysis_dft_filterbank(xData, Ndown, Ndft, aPrototype, 'pfs', commutator='cw', verbosity=vb)\n", + "Ac3 = analysis_dft_filterbank(xData, Ndown, Ndft, aPrototype, 'pfs', commutator='ccw', verbosity=vb)\n", + "verify_result(np.all(np.isclose(Ac0, Ac1)))\n", + "verify_result(np.all(np.isclose(Ac0, Ac2)))\n", + "verify_result(np.all(np.isclose(Ac2, Ac3)))" + ] + }, + { + "cell_type": "code", + "execution_count": 38, "id": "b0f997d3", "metadata": { - "scrolled": true + "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "128\n", - "> analysis_dft_filterbank():\n", - " . len(x) = 384\n", - " . Nx = 373\n", - " . Nblocks = 32\n", - " . Ros = 1.3333333333333333\n", - " . Ndown = 12\n", - " . Ndft = 16\n", - " . commutator = ccw\n", - "\n", "PASSED\n" ] } ], "source": [ - "Ac = analysis_dft_filterbank(xData, Ndown, Ndft, hPrototype, commutator='ccw')\n", + "Ac = Ac0\n", "yDownBin = Ac[kLo]\n", + "#yDownBin = yDownBin[:len(yDown)]\n", "\n", "result = np.all(np.isclose(yDown, yDownBin))\n", "if not result:\n", @@ -1241,6 +1320,8 @@ " plt.plot(m_sub, yDown.imag, 'g.--')\n", " plt.plot(m_sub, yDownBin.real, 'r.-')\n", " plt.plot(m_sub, yDownBin.imag, 'r.--')\n", + " #plt.plot(m_sub, yDown.real - yDownBin.real, 'b.-')\n", + " #plt.plot(m_sub, yDown.imag - yDownBin.imag, 'b.--')\n", " plt.xlim([0, 20])\n", "verify_result(result)" ] @@ -1255,7 +1336,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 39, "id": "47d5bf5b", "metadata": {}, "outputs": [ @@ -1268,8 +1349,10 @@ " . Ros = 1.3333333333333333\n", " . Nup = 12\n", " . Ndft = 16\n", - " . commutator = cw\n", - "\n" + " . structure = wola\n", + " . commutator = ccw\n", + "\n", + "PASSED\n" ] } ], @@ -1285,20 +1368,20 @@ " Yc[Ndft - kLo] = np.conjugate(yDownBin)\n", "\n", "# Single bin synthesis from PFB\n", - "yr = synthesis_dft_filterbank(Yc, Nup, Ndft, fPrototype, commutator='cw')\n", + "yr = synthesis_dft_filterbank(Yc, Nup, Ndft, sPrototype, structure='wola', commutator='ccw')\n", "yr = yr[0 : len(yrUpLpfLo)]\n", "\n", "result = np.all(np.isclose(yrUpLpfLo, yr))\n", "if not result:\n", " plt.plot(n_sub, yrUpLpfLo, 'g.-')\n", - " plt.plot(n_sub, yr, 'r-')\n", - " plt.xlim((20, 21))\n", - "#verify_result(result)" + " plt.plot(n_sub, yr, 'r.-')\n", + " #plt.xlim((20, 21))\n", + "verify_result(result)" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 40, "id": "4818ad5a", "metadata": { "scrolled": true @@ -1313,10 +1396,13 @@ " . Ros = 1.3333333333333333\n", " . Nup = 12\n", " . Ndft = 16\n", + " . structure = wola\n", " . commutator = ccw\n", "\n", "wgSub = 1.500000\n", "kLo = 2\n", + "xLen = 129 input samples\n", + "yrSNR = 5.98613 [dB], single bin\n", "SNR_yr = 5.98613 [dB], single bin\n", "SNR_sr = 44.05325 [dB], all bins\n" ] @@ -1336,7 +1422,7 @@ "# . Use yr for comparison between single channel pipeline and using PFB for single bin\n", "# . Use sr to see impact of aliasing in the other bins, due to limited stopband attenuation\n", "# of both LPF.\n", - "sr = synthesis_dft_filterbank(Ac, Nup, Ndft, fPrototype, commutator='ccw')\n", + "sr = synthesis_dft_filterbank(Ac, Nup, Ndft, sPrototype, structure='wola', commutator='ccw')\n", "sr = sr[0 : len(yrUpLpfLo)]\n", "\n", "# Output time aligned with input\n", @@ -1351,32 +1437,37 @@ "# . The SNR for CW input calculated for refBunton and with construct.m are about equal within \n", "# +-0.0005 dB. The difference is probably due to that the selected ranges for calculating the\n", "# SNR are not exactly the same. Varying the offset shows a similar spread in SNR.\n", - "offset = Ncoefs # + Ncoefs // 7\n", - "if len(xDelayed[offset:]) < Ncoefs:\n", - " print('Too low Nsim for proper SNR calculation')\n", + "offset = Ncoefs\n", + "#offset = 10000\n", + "endset = 70000\n", + "xLen = len(xDelayed[offset:endset])\n", + "if xLen < Ncoefs:\n", + " print('Too low Nsim for proper SNR_yr calculation %d < %d' % (xLen, Ncoefs))\n", " verify_result(False)\n", - "SNR_yr = snr_db(xDelayed[offset:], xDelayed[offset:] - x_yr[offset:])\n", - "SNR_sr = snr_db(xDelayed[offset:], xDelayed[offset:] - x_sr[offset:])\n", + "SNR_yr = snr_db(xDelayed[offset:endset], xDelayed[offset:endset] - x_yr[offset:endset])\n", + "SNR_sr = snr_db(xDelayed[offset:endset], xDelayed[offset:endset] - x_sr[offset:endset])\n", "\n", "print('wgSub = %f' % wgSub)\n", "print('kLo = %d' % kLo)\n", + "print('xLen = %d input samples' % xLen)\n", + "print('yrSNR = %.5f [dB], single bin' % yrSNR)\n", "print('SNR_yr = %.5f [dB], single bin' % SNR_yr)\n", "print('SNR_sr = %.5f [dB], all bins' % SNR_sr)" ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 41, "id": "2c1ec80d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[<matplotlib.lines.Line2D at 0x7fcffa407f40>]" + "[<matplotlib.lines.Line2D at 0x7f06515e56a0>]" ] }, - "execution_count": 38, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" }, @@ -1392,31 +1483,29 @@ } ], "source": [ - "# Plot original real xData recovered yr and sr\n", + "# Plot original real xData recovered with sr for all bins\n", "plt.plot(xDelayed[offset:], 'r.-')\n", - "#lt.plot(x_yr[offset:], 'b.-')\n", - "#plt.plot(x_yr[offset:] - xDelayed[offset:], 'm.-')\n", "plt.plot(x_sr[offset:], 'g.-')\n", "plt.plot(x_sr[offset:] - xDelayed[offset:], 'y.-')\n", - "#plt.xlim([0, 1250])\n", + "#plt.xlim([0, 2500])\n", "#plt.ylim([-0.01, 0.01])" ] }, { "cell_type": "code", "execution_count": null, - "id": "a16ef270", + "id": "758be965", "metadata": {}, "outputs": [], "source": [] }, { - "cell_type": "markdown", - "id": "77c01d0d", + "cell_type": "code", + "execution_count": null, + "id": "5eb4fafb", "metadata": {}, - "source": [ - "# " - ] + "outputs": [], + "source": [] } ], "metadata": { -- GitLab