diff --git a/.github/workflows/darker.yaml b/.github/workflows/darker.yaml deleted file mode 100644 index c7a58dc8c..000000000 --- a/.github/workflows/darker.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: Lint with Darker - -on: [push, pull_request] - -jobs: - lint-with-darker: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4.1.1 - with: - fetch-depth: 0 - - uses: akaihola/darker@1.7.3 - with: - options: "--check --diff" - src: "./src" - revision: "origin/main..." - version: "@e3c210b5c1b91400c3f317b2474c10ab23bec1cf" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d5ea87d2..1db76e57f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,15 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.2.2' + rev: 'v0.3.0' hooks: + # Run the linter - id: ruff types_or: [python, pyi, jupyter, toml] args: [ --fix, --exit-non-zero-on-fix ] + # Run the formatter + - id: ruff-format + types_or: [ python, pyi, jupyter ] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: @@ -18,7 +22,3 @@ repos: - id: debug-statements - id: mixed-line-ending args: ['--fix=no'] - - repo: https://github.com/akaihola/darker - rev: 1.7.2 - hooks: - - id: darker diff --git a/docs/examples/Example_Write_Read_JSON.ipynb b/docs/examples/Example_Write_Read_JSON.ipynb index 4c30877b1..3020a1ecf 100644 --- a/docs/examples/Example_Write_Read_JSON.ipynb +++ b/docs/examples/Example_Write_Read_JSON.ipynb @@ -22,8 +22,8 @@ "import broadbean as bb\n", "from broadbean.plotting import plotter\n", "\n", - "mpl.rcParams['figure.figsize'] = (8, 3)\n", - "mpl.rcParams['figure.subplot.bottom'] = 0.15 " + "mpl.rcParams[\"figure.figsize\"] = (8, 3)\n", + "mpl.rcParams[\"figure.subplot.bottom\"] = 0.15" ] }, { @@ -997,7 +997,7 @@ "bp_square = bb.BluePrint()\n", "bp_square.setSR(1e9)\n", "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", - "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name='top', dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", "bp_boxes = bp_square + bp_square\n", "plotter(bp_boxes)" @@ -1016,7 +1016,7 @@ "metadata": {}, "outputs": [], "source": [ - "bp_boxes.write_to_json('blue.json')" + "bp_boxes.write_to_json(\"blue.json\")" ] }, { @@ -1976,8 +1976,10 @@ } ], "source": [ - "bp_boxes_read = bb.BluePrint.init_from_json('blue.json')\n", - "bp_boxes_read.setSR(1e9) # note the blueprint readback do not have a sample rate attached\n", + "bp_boxes_read = bb.BluePrint.init_from_json(\"blue.json\")\n", + "bp_boxes_read.setSR(\n", + " 1e9\n", + ") # note the blueprint readback do not have a sample rate attached\n", "plotter(bp_boxes_read)" ] }, @@ -2969,7 +2971,7 @@ "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", "seq1 = bb.Sequence()\n", "\n", - "# We fill up the sequence by adding elements at different sequence positions. \n", + "# We fill up the sequence by adding elements at different sequence positions.\n", "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", "\n", "#\n", @@ -2979,7 +2981,7 @@ "bp_square = bb.BluePrint()\n", "bp_square.setSR(1e9)\n", "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", - "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name='top', dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", "bp_boxes = bp_square + bp_square\n", "#\n", @@ -2988,10 +2990,14 @@ "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", "bp_sineandboxes = bp_sine + bp_square\n", "\n", - "bp_sineandboxes.setSegmentMarker('ramp', (-0.0, 100e-9), 1) # segment name, (delay, duration), markerID\n", - "bp_sineandboxes.setSegmentMarker('sine', (-0.0, 100e-9), 2) # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", "# make marker 2 go ON halfway through the sine\n", - "#bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", "\n", "\n", "# create elements\n", @@ -3958,7 +3964,7 @@ } ], "source": [ - "elem1.write_to_json('elem.json')\n", + "elem1.write_to_json(\"elem.json\")\n", "plotter(elem1)" ] }, @@ -3975,7 +3981,7 @@ "metadata": {}, "outputs": [], "source": [ - "elem_read = bb.Element.init_from_json('elem.json')" + "elem_read = bb.Element.init_from_json(\"elem.json\")" ] }, { @@ -3999,7 +4005,9 @@ } ], "source": [ - "print(elem1.description == elem_read.description) ## note that the elements are not identical only the discription" + "print(\n", + " elem1.description == elem_read.description\n", + ") ## note that the elements are not identical only the discription" ] }, { @@ -4966,7 +4974,7 @@ "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", "seq1 = bb.Sequence()\n", "\n", - "# We fill up the sequence by adding elements at different sequence positions. \n", + "# We fill up the sequence by adding elements at different sequence positions.\n", "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", "\n", "#\n", @@ -4976,7 +4984,7 @@ "bp_square = bb.BluePrint()\n", "bp_square.setSR(1e9)\n", "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", - "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name='top', dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", "bp_boxes = bp_square + bp_square\n", "#\n", @@ -4985,10 +4993,14 @@ "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", "bp_sineandboxes = bp_sine + bp_square\n", "\n", - "bp_sineandboxes.setSegmentMarker('ramp', (-0.0, 100e-9), 1) # segment name, (delay, duration), markerID\n", - "bp_sineandboxes.setSegmentMarker('sine', (-0.0, 100e-9), 2) # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", "# make marker 2 go ON halfway through the sine\n", - "#bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", "\n", "\n", "# create elements\n", @@ -5011,7 +5023,7 @@ "seq1.setChannelAmplitude(1, 10e-3) # Call signature: channel, amplitude (peak-to-peak)\n", "seq1.setChannelOffset(1, 0)\n", "seq1.setChannelAmplitude(3, 10e-3)\n", - "seq1.setChannelOffset(3, 0) \n", + "seq1.setChannelOffset(3, 0)\n", "\n", "# Here we repeat each element twice and then proceed to the next, wrapping over at the end\n", "seq1.setSequencingTriggerWait(1, 0)\n", @@ -5044,8 +5056,7 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "seq1.write_to_json('testdata.json')" + "seq1.write_to_json(\"testdata.json\")" ] }, { @@ -5054,7 +5065,7 @@ "metadata": {}, "outputs": [], "source": [ - "bp_boxes.write_to_json('blue.json')" + "bp_boxes.write_to_json(\"blue.json\")" ] }, { @@ -5070,7 +5081,7 @@ "metadata": {}, "outputs": [], "source": [ - "seq = bb.Sequence.init_from_json('testdata.json')" + "seq = bb.Sequence.init_from_json(\"testdata.json\")" ] }, { @@ -6064,9 +6075,9 @@ "metadata": {}, "outputs": [], "source": [ - "def readblueprint(path:str,element:int = 1,channel:int = 1):\n", - " tempseq = bb.Sequence.init_from_json(path)\n", - " return tempseq.element(element)._data[channel]['blueprint']" + "def readblueprint(path: str, element: int = 1, channel: int = 1):\n", + " tempseq = bb.Sequence.init_from_json(path)\n", + " return tempseq.element(element)._data[channel][\"blueprint\"]" ] }, { @@ -6075,7 +6086,9 @@ "metadata": {}, "outputs": [], "source": [ - "def writeblueprint(blueprint, path:str,SR:float = 1e9,SeqAmp:float = 10e-3,SeqOffset:float = 0):# -> None\n", + "def writeblueprint(\n", + " blueprint, path: str, SR: float = 1e9, SeqAmp: float = 10e-3, SeqOffset: float = 0\n", + "): # -> None\n", " if blueprint.SR is None:\n", " blueprint.setSR(SR)\n", " elemtmp = bb.Element()\n", @@ -6094,7 +6107,7 @@ "metadata": {}, "outputs": [], "source": [ - "writeblueprint(bp_boxes,'boxes.json')" + "writeblueprint(bp_boxes, \"boxes.json\")" ] }, { @@ -7047,7 +7060,7 @@ } ], "source": [ - "bp_one_two = readblueprint('testdata.json',element=2,channel=1)\n", + "bp_one_two = readblueprint(\"testdata.json\", element=2, channel=1)\n", "plotter(bp_one_two)" ] }, diff --git a/docs/examples/Filter_compensation.ipynb b/docs/examples/Filter_compensation.ipynb index 6e874e04b..31411502a 100644 --- a/docs/examples/Filter_compensation.ipynb +++ b/docs/examples/Filter_compensation.ipynb @@ -58,12 +58,11 @@ "outputs": [], "source": [ "def squarewave(npts, periods=5):\n", - "\n", " periods = int(periods)\n", " array = np.zeros(npts)\n", "\n", " for n in range(periods):\n", - " array[int(n*npts/periods): int((2*n+1)*npts/2/periods)] = 1\n", + " array[int(n * npts / periods) : int((2 * n + 1) * npts / 2 / periods)] = 1\n", "\n", " return array" ] @@ -88,10 +87,10 @@ "\n", "f_cut = 12 # filter cut-off frequency (Hz)\n", "\n", - "time = np.linspace(0, npts/SR, npts)\n", + "time = np.linspace(0, npts / SR, npts)\n", "T = time[-1]\n", "\n", - "signal1 = np.sin(2*np.pi*5/T*time)+0.7*np.cos(2*np.pi*1/T*time)\n", + "signal1 = np.sin(2 * np.pi * 5 / T * time) + 0.7 * np.cos(2 * np.pi * 1 / T * time)\n", "signal2 = squarewave(npts, periods=4)" ] }, @@ -103,7 +102,7 @@ "source": [ "# Then we may high-pass them or low-pass them\n", "\n", - "filtertype = 'HP'\n", + "filtertype = \"HP\"\n", "# filtertype = 'LP'\n", "\n", "signal1_filt = applyRCFilter(signal1, SR, filtertype, f_cut, order=1)\n", @@ -909,16 +908,16 @@ "# And inspect the result\n", "\n", "fig, axs = plt.subplots(2, 1)\n", - "axs[0].plot(time, signal1, label='Input signal')\n", - "axs[0].plot(time, signal1_filt, label='Filtered')\n", - "axs[0].set_xlabel('Time (s)')\n", - "axs[0].set_ylabel('Signal (a.u.)')\n", + "axs[0].plot(time, signal1, label=\"Input signal\")\n", + "axs[0].plot(time, signal1_filt, label=\"Filtered\")\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a.u.)\")\n", "axs[0].legend()\n", "\n", - "axs[1].plot(time, signal2, label='Input signal')\n", - "axs[1].plot(time, signal2_filt, label='Filtered')\n", - "axs[1].set_xlabel('Time (s)')\n", - "axs[1].set_ylabel('Signal (a.u.)')\n", + "axs[1].plot(time, signal2, label=\"Input signal\")\n", + "axs[1].plot(time, signal2_filt, label=\"Filtered\")\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a.u.)\")\n", "axs[1].legend()\n", "\n", "plt.tight_layout()" @@ -1741,16 +1740,16 @@ ], "source": [ "fig, axs = plt.subplots(2, 1)\n", - "axs[0].plot(time, signal1, label='Input signal')\n", - "axs[0].plot(time, signal1_comp, label='Compensated')\n", - "axs[0].set_xlabel('Time (s)')\n", - "axs[0].set_ylabel('Signal (a.u.)')\n", + "axs[0].plot(time, signal1, label=\"Input signal\")\n", + "axs[0].plot(time, signal1_comp, label=\"Compensated\")\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a.u.)\")\n", "axs[0].legend()\n", "\n", - "axs[1].plot(time, signal2, label='Input signal')\n", - "axs[1].plot(time, signal2_comp, label='Compensated')\n", - "axs[1].set_xlabel('Time (s)')\n", - "axs[1].set_ylabel('Signal (a.u.)')\n", + "axs[1].plot(time, signal2, label=\"Input signal\")\n", + "axs[1].plot(time, signal2_comp, label=\"Compensated\")\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a.u.)\")\n", "axs[1].legend()\n", "\n", "plt.tight_layout()" @@ -2563,19 +2562,19 @@ "signal2_check2 = applyInverseRCFilter(signal2_filt, SR, filtertype, f_cut, order=1)\n", "\n", "fig, axs = plt.subplots(2, 1)\n", - "axs[0].set_title('Signal 1')\n", - "axs[0].plot(time, signal1_check1, label='Check 1')\n", - "axs[0].plot(time, signal1_check2, label='Check 2')\n", + "axs[0].set_title(\"Signal 1\")\n", + "axs[0].plot(time, signal1_check1, label=\"Check 1\")\n", + "axs[0].plot(time, signal1_check2, label=\"Check 2\")\n", "axs[0].legend()\n", - "axs[0].set_xlabel('Time (s)')\n", - "axs[0].set_ylabel('Signal (a. u.)')\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a. u.)\")\n", "\n", - "axs[1].set_title('Signal 2')\n", - "axs[1].plot(time, signal2_check1, label='Check 1')\n", - "axs[1].plot(time, signal2_check2, label='Check 2')\n", + "axs[1].set_title(\"Signal 2\")\n", + "axs[1].plot(time, signal2_check1, label=\"Check 1\")\n", + "axs[1].plot(time, signal2_check2, label=\"Check 2\")\n", "axs[1].legend()\n", - "axs[1].set_xlabel('Time (s)')\n", - "axs[1].set_ylabel('Signal (a. u.)')\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a. u.)\")\n", "\n", "plt.tight_layout()" ] @@ -3395,21 +3394,25 @@ "\n", "tf_freq = np.linspace(0, 1000, tf_points)\n", "tf_amp = np.zeros(tf_points)\n", - "tf_amp += np.hanning(2*tf_points)[tf_points:][::-1] # A high-pass \n", - "tf_amp += 0.1 # Note: a transfer function with very low values give unphysical compensation\n", + "tf_amp += np.hanning(2 * tf_points)[tf_points:][::-1] # A high-pass\n", + "tf_amp += (\n", + " 0.1 # Note: a transfer function with very low values give unphysical compensation\n", + ")\n", "tf_amp /= tf_amp.max()\n", "\n", "# and some experimental noise\n", "tf_noise_amp = 0.02\n", - "tf_amp += np.convolve(0.02*np.random.randn(tf_points),\n", - " np.array([0.5, 1, 0.5]), mode='same')/2\n", + "tf_amp += (\n", + " np.convolve(0.02 * np.random.randn(tf_points), np.array([0.5, 1, 0.5]), mode=\"same\")\n", + " / 2\n", + ")\n", "\n", "# And then our favourite signal: the square wave\n", "\n", "sig_points = 1000\n", - "signal = squarewave(sig_points, periods=10) + 0.01*np.random.randn(1000)\n", + "signal = squarewave(sig_points, periods=10) + 0.01 * np.random.randn(1000)\n", "SR = 2000 # We pretend that the signal was sampled with this sample rate\n", - "time = np.linspace(0, sig_points/SR, sig_points)\n", + "time = np.linspace(0, sig_points / SR, sig_points)\n", "\n", "# Then we may apply the filter we made\n", "\n", @@ -3417,29 +3420,31 @@ "\n", "# Or conversely, we may apply its inverse\n", "\n", - "signal_compensated = applyCustomTransferFunction(signal, SR, tf_freq, tf_amp, invert=True)\n", + "signal_compensated = applyCustomTransferFunction(\n", + " signal, SR, tf_freq, tf_amp, invert=True\n", + ")\n", "\n", "# Finally, it's nice to visualise things\n", "\n", "fig, axs = plt.subplots(2, 2)\n", "axs[0, 0].plot(tf_freq, tf_amp)\n", - "axs[0, 0].set_xlabel('Freq. (Hz)')\n", - "axs[0, 0].set_ylabel('Transfer func.')\n", + "axs[0, 0].set_xlabel(\"Freq. (Hz)\")\n", + "axs[0, 0].set_ylabel(\"Transfer func.\")\n", "\n", - "axs[0, 1].plot(time, signal, color='#ff9900')\n", - "axs[0, 1].set_xlabel('Time (s)')\n", - "axs[0, 1].set_ylabel('Sig. ampl. (arb. un.)')\n", - "axs[0, 1].set_title('Input')\n", + "axs[0, 1].plot(time, signal, color=\"#ff9900\")\n", + "axs[0, 1].set_xlabel(\"Time (s)\")\n", + "axs[0, 1].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[0, 1].set_title(\"Input\")\n", "\n", - "axs[1, 0].plot(time, signal_filtered, color='#339966')\n", - "axs[1, 0].set_xlabel('Time (s)')\n", - "axs[1, 0].set_ylabel('Sig. ampl. (arb. un.)')\n", - "axs[1, 0].set_title('Filtered')\n", + "axs[1, 0].plot(time, signal_filtered, color=\"#339966\")\n", + "axs[1, 0].set_xlabel(\"Time (s)\")\n", + "axs[1, 0].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[1, 0].set_title(\"Filtered\")\n", "\n", - "axs[1, 1].plot(time, signal_compensated, color='#990033')\n", - "axs[1, 1].set_xlabel('Time (s)')\n", - "axs[1, 1].set_ylabel('Sig.ampl. (arb. un.)')\n", - "axs[1, 1].set_title('Compensated')\n", + "axs[1, 1].plot(time, signal_compensated, color=\"#990033\")\n", + "axs[1, 1].set_xlabel(\"Time (s)\")\n", + "axs[1, 1].set_ylabel(\"Sig.ampl. (arb. un.)\")\n", + "axs[1, 1].set_title(\"Compensated\")\n", "\n", "plt.tight_layout()" ] diff --git a/docs/examples/Making_output_for_Tektronix_AWG70000A.ipynb b/docs/examples/Making_output_for_Tektronix_AWG70000A.ipynb index ac6cd5ed3..6fe524d0a 100644 --- a/docs/examples/Making_output_for_Tektronix_AWG70000A.ipynb +++ b/docs/examples/Making_output_for_Tektronix_AWG70000A.ipynb @@ -49,11 +49,13 @@ "baseshape = bb.BluePrint()\n", "baseshape.insertSegment(0, ramp, (0, 0), dur=t1)\n", "baseshape.insertSegment(1, ramp, (0, high_level), dur=ramp_time)\n", - "baseshape.insertSegment(2, sine, (f0, sine_amp, high_level, 0), dur=t_sine, name='drive')\n", + "baseshape.insertSegment(\n", + " 2, sine, (f0, sine_amp, high_level, 0), dur=t_sine, name=\"drive\"\n", + ")\n", "baseshape.insertSegment(3, ramp, (high_level, 0), dur=ramp_time)\n", - "baseshape.insertSegment(4, ramp, (0, 0), dur=t2, name='wait')\n", - "baseshape.setSegmentMarker('wait', (0, t_sine), 1)\n", - "baseshape.setSegmentMarker('drive', (0, t_sine), 2)\n", + "baseshape.insertSegment(4, ramp, (0, 0), dur=t2, name=\"wait\")\n", + "baseshape.setSegmentMarker(\"wait\", (0, t_sine), 1)\n", + "baseshape.setSegmentMarker(\"drive\", (0, t_sine), 2)\n", "baseshape.setSR(SR)" ] }, @@ -879,14 +881,14 @@ "\n", "for index, freq in enumerate(sine_freqs):\n", " elem = baseelem.copy()\n", - " elem.changeArg(1, 'drive', 'freq', freq)\n", - " seq.addElement(index+1, elem)\n", - " seq.setSequencingTriggerWait(index+1, 1) # 1: trigA, 2: trigB, 3: EXT\n", + " elem.changeArg(1, \"drive\", \"freq\", freq)\n", + " seq.addElement(index + 1, elem)\n", + " seq.setSequencingTriggerWait(index + 1, 1) # 1: trigA, 2: trigB, 3: EXT\n", "\n", "# and set the last element to point back to the first one\n", - "seq.setSequencingGoto(index+1, 1)\n", + "seq.setSequencingGoto(index + 1, 1)\n", "\n", - "seq.name = 'tutorial_sequence' # the sequence name will be needed later" + "seq.name = \"tutorial_sequence\" # the sequence name will be needed later" ] }, { @@ -915,7 +917,7 @@ "\n", "from qcodes.instrument_drivers.tektronix.AWG70002A import AWG70002A\n", "\n", - "awg = AWG70002A('awg', 'TCPIP0::172.20.2.243::inst0::INSTR')" + "awg = AWG70002A(\"awg\", \"TCPIP0::172.20.2.243::inst0::INSTR\")" ] }, { @@ -972,7 +974,7 @@ "outputs": [], "source": [ "# transfer it to the awg harddrive\n", - "awg.sendSEQXFile(seqx_output, 'tutorial.seqx')" + "awg.sendSEQXFile(seqx_output, \"tutorial.seqx\")" ] }, { @@ -982,7 +984,7 @@ "outputs": [], "source": [ "# load it into awg active memory\n", - "awg.loadSEQXFile('tutorial.seqx')" + "awg.loadSEQXFile(\"tutorial.seqx\")" ] }, { @@ -994,7 +996,7 @@ "outputs": [], "source": [ "# assign tracks from the sequence to the awg sequencer\n", - "awg.ch1.setSequenceTrack('tutorial_sequence', 1)\n", + "awg.ch1.setSequenceTrack(\"tutorial_sequence\", 1)\n", "\n", "# NB: Each channel has an assigned resolution, either 8, 9, or 10.\n", "# 8 means 8 bits for the waveform, 2 bits for the markers (i.e. 2 markers)\n", diff --git a/docs/examples/Pulse_Building_Tutorial.ipynb b/docs/examples/Pulse_Building_Tutorial.ipynb index 7ac7ae399..532391a33 100644 --- a/docs/examples/Pulse_Building_Tutorial.ipynb +++ b/docs/examples/Pulse_Building_Tutorial.ipynb @@ -81,8 +81,8 @@ "import broadbean as bb\n", "from broadbean.plotting import plotter\n", "\n", - "mpl.rcParams['figure.figsize'] = (8, 3)\n", - "mpl.rcParams['figure.subplot.bottom'] = 0.15 " + "mpl.rcParams[\"figure.figsize\"] = (8, 3)\n", + "mpl.rcParams[\"figure.subplot.bottom\"] = 0.15" ] }, { @@ -131,7 +131,7 @@ "\n", "# the blueprint is filled via the insertSegment method\n", "# Call signature: position in the blueprint, function, args, name, duration\n", - "bp1.insertSegment(0, ramp, (0, 1e-3), name='', dur=3e-6)\n", + "bp1.insertSegment(0, ramp, (0, 1e-3), name=\"\", dur=3e-6)\n", "\n", "# A sample rate can be set (Sa/S). Without a sample rate, we can not plot the blueprint.\n", "bp1.setSR(1e9)\n", @@ -140,9 +140,11 @@ "bp1.showPrint()\n", "\n", "# more segments can be added...\n", - "bp1.insertSegment(1, sine, (5e5, 1e-3, 1e-3, 0), name='mysine', dur=2e-6)\n", - "bp1.insertSegment(2, ramp, (1e-3, 0), name='', dur=3e-6)\n", - "bp1.insertSegment(3, arb_func, (lambda t, ampl: ampl * t * t, {\"ampl\": 5e8}), name='myfunc', dur=2e-6)\n", + "bp1.insertSegment(1, sine, (5e5, 1e-3, 1e-3, 0), name=\"mysine\", dur=2e-6)\n", + "bp1.insertSegment(2, ramp, (1e-3, 0), name=\"\", dur=3e-6)\n", + "bp1.insertSegment(\n", + " 3, arb_func, (lambda t, ampl: ampl * t * t, {\"ampl\": 5e8}), name=\"myfunc\", dur=2e-6\n", + ")\n", "\n", "# ... and reinspected\n", "bp1.showPrint()" @@ -204,7 +206,7 @@ "plotter(bp2)\n", "\n", "# Segments may be removed from a blueprint. They are removed by name.\n", - "bp2.removeSegment('ramp2')\n", + "bp2.removeSegment(\"ramp2\")\n", "plotter(bp2)" ] }, @@ -225,9 +227,9 @@ ], "source": [ "# A blueprint has a handful of different lengths one may check\n", - "print(f'Number of points in blueprint: {bp1.points}')\n", - "print(f'Length of blueprint in seconds: {bp1.duration}')\n", - "print(f'Number of segments in blueprint: {bp1.length_segments}')\n" + "print(f\"Number of points in blueprint: {bp1.points}\")\n", + "print(f\"Length of blueprint in seconds: {bp1.duration}\")\n", + "print(f\"Number of segments in blueprint: {bp1.length_segments}\")" ] }, { @@ -344,11 +346,15 @@ "bp_rtm.setSR(100)\n", "bp_rtm.insertSegment(0, ramp, (0, 1), dur=1)\n", "bp_rtm.insertSegment(1, ramp, (1, 1), dur=1)\n", - "bp_rtm.insertSegment(2, sine, (1.675, 1, 0, np.pi/2), dur=1.5, name='mysine') # This is the important segment\n", + "bp_rtm.insertSegment(\n", + " 2, sine, (1.675, 1, 0, np.pi / 2), dur=1.5, name=\"mysine\"\n", + ") # This is the important segment\n", "# make marker 1 go ON a bit before the sine comes on\n", - "bp_rtm.setSegmentMarker('mysine', (-0.1, 0.2), 1) # segment name, (delay, duration), markerID\n", + "bp_rtm.setSegmentMarker(\n", + " \"mysine\", (-0.1, 0.2), 1\n", + ") # segment name, (delay, duration), markerID\n", "# make marker 2 go ON halfway through the sine\n", - "bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "bp_rtm.setSegmentMarker(\"mysine\", (0.75, 0.1), 2)\n", "\n", "plotter(bp_rtm)\n", "\n", @@ -359,7 +365,7 @@ "plotter(bp_rtm)\n", "\n", "\n", - "# NB: the two different ways of inputting markers will never directly conflict, since one only specifies when to turn \n", + "# NB: the two different ways of inputting markers will never directly conflict, since one only specifies when to turn\n", "# markers ON. It is up to the user to ensure that markers switch off again as expected, i.e. that different marker\n", "# specifications do not overlap." ] @@ -450,22 +456,24 @@ "bp_mod = bb.BluePrint()\n", "bp_mod.setSR(100)\n", "\n", - "bp_mod.insertSegment(0, ramp, (0, 0), name='before', dur=1)\n", - "bp_mod.insertSegment(1, ramp, (1, 1), name='plateau', dur=1)\n", - "bp_mod.insertSegment(2, ramp, (0, 0), name='after', dur=1)\n", + "bp_mod.insertSegment(0, ramp, (0, 0), name=\"before\", dur=1)\n", + "bp_mod.insertSegment(1, ramp, (1, 1), name=\"plateau\", dur=1)\n", + "bp_mod.insertSegment(2, ramp, (0, 0), name=\"after\", dur=1)\n", "\n", "plotter(bp_mod)\n", "\n", "# Functional arguments can be changed\n", "\n", "# They are looked up by segment name\n", - "bp_mod.changeArg('before', 'stop', 1) # the argument to change may either be the argument name or its position\n", - "bp_mod.changeArg('after', 0, 1)\n", + "bp_mod.changeArg(\n", + " \"before\", \"stop\", 1\n", + ") # the argument to change may either be the argument name or its position\n", + "bp_mod.changeArg(\"after\", 0, 1)\n", "\n", "plotter(bp_mod)\n", "\n", "# Durations can also be changed\n", - "bp_mod.changeDuration('plateau', 2)\n", + "bp_mod.changeDuration(\"plateau\", 2)\n", "\n", "plotter(bp_mod)" ] @@ -537,14 +545,14 @@ "bp_wait.setSR(100)\n", "\n", "bp_wait.insertSegment(0, ramp, (0, 0), dur=1)\n", - "bp_wait.insertSegment(1, ramp, (1, 1), name='plateau', dur=1)\n", + "bp_wait.insertSegment(1, ramp, (1, 1), name=\"plateau\", dur=1)\n", "# function must be sthe string 'waituntil', the argument is the ABSOLUTE time to wait until\n", - "bp_wait.insertSegment(2, 'waituntil', (5,)) \n", - "bp_wait.insertSegment(3, sine, (1, 0.1, 0, -np.pi/2), dur=1)\n", + "bp_wait.insertSegment(2, \"waituntil\", (5,))\n", + "bp_wait.insertSegment(3, sine, (1, 0.1, 0, -np.pi / 2), dur=1)\n", "plotter(bp_wait)\n", "\n", "# If we make the square pulse longer, the sine still occurs at 5 s\n", - "bp_wait.changeDuration('plateau', 1.5)\n", + "bp_wait.changeDuration(\"plateau\", 1.5)\n", "plotter(bp_wait)" ] }, @@ -593,7 +601,7 @@ "bp_square = bb.BluePrint()\n", "bp_square.setSR(1e9)\n", "bp_square.insertSegment(0, ramp, (0, 0), dur=0.1e-6)\n", - "bp_square.insertSegment(1, ramp, (10e-3, 10e-3), name='top', dur=0.1e-6)\n", + "bp_square.insertSegment(1, ramp, (10e-3, 10e-3), name=\"top\", dur=0.1e-6)\n", "bp_square.insertSegment(2, ramp, (0, 0), dur=0.1e-6)\n", "bp_boxes = bp_square + bp_square\n", "#\n", @@ -631,9 +639,9 @@ ], "source": [ "# An element has several features\n", - "print(f'Designated channels: {elem1.channels}')\n", - "print(f'Total duration: {elem1.duration} s')\n", - "print(f'Sample rate: {elem1.SR} (Sa/S)')" + "print(f\"Designated channels: {elem1.channels}\")\n", + "print(f\"Total duration: {elem1.duration} s\")\n", + "print(f\"Sample rate: {elem1.SR} (Sa/S)\")" ] }, { @@ -668,11 +676,15 @@ "# We can modify the blueprints of an element through the element object\n", "\n", "# Change the sine freq\n", - "elem1.changeArg(3, 'sine', 'freq', 6.67e6 ) # Call signature: channel, segment name, argument, new_value\n", + "elem1.changeArg(\n", + " 3, \"sine\", \"freq\", 6.67e6\n", + ") # Call signature: channel, segment name, argument, new_value\n", "\n", "# make the second plateaus last longer\n", - "elem1.changeDuration(1, 'top2', 0.2e-6) # In this blueprint, the second top is called top2\n", - "elem1.changeDuration(3, 'top', 0.2e-6)\n", + "elem1.changeDuration(\n", + " 1, \"top2\", 0.2e-6\n", + ") # In this blueprint, the second top is called top2\n", + "elem1.changeDuration(3, \"top\", 0.2e-6)\n", "\n", "plotter(elem1)" ] @@ -695,7 +707,7 @@ "source": [ "seq1 = bb.Sequence()\n", "\n", - "# We fill up the sequence by adding elements at different sequence positions. \n", + "# We fill up the sequence by adding elements at different sequence positions.\n", "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", "\n", "#\n", @@ -705,7 +717,7 @@ "bp_square = bb.BluePrint()\n", "bp_square.setSR(1e9)\n", "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", - "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name='top', dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", "bp_boxes = bp_square + bp_square\n", "#\n", @@ -854,7 +866,7 @@ "seq1.setChannelAmplitude(1, 10e-3) # Call signature: channel, amplitude (peak-to-peak)\n", "seq1.setChannelOffset(1, 0)\n", "seq1.setChannelAmplitude(3, 10e-3)\n", - "seq1.setChannelOffset(3, 0) \n", + "seq1.setChannelOffset(3, 0)\n", "\n", "# Here we repeat each element twice and then proceed to the next, wrapping over at the end\n", "seq1.setSequencingTriggerWait(1, 0)\n", @@ -903,7 +915,9 @@ "chan1_awg_input = package[0] # returns a tuple yielding an awg file with channel 1\n", "chan3_awg_input = package[1] # returns a tuple yielding an awg file with channel 3\n", "\n", - "both_chans_awg_input = package[:] # returns a tuple yielding an awg file with both channels\n", + "both_chans_awg_input = package[\n", + " :\n", + "] # returns a tuple yielding an awg file with both channels\n", "\n", "# This may be useful to make one big sequence for one experiment and then uploading part of it to one awg\n", "# and part of it to another (since physical awg's usually don't have enough channels for a big experiment)\n", @@ -946,9 +960,9 @@ "seq1.setChannelDelay(3, 123e-9)\n", "\n", "# To apply for a high pass filter with a cut-off frequency of 1 MHz on channel 3, we can do\n", - "seq1.setChannelFilterCompensation(3, 'HP', order=1, f_cut=1e6)\n", + "seq1.setChannelFilterCompensation(3, \"HP\", order=1, f_cut=1e6)\n", "# or, equivalently,\n", - "seq1.setChannelFilterCompensation(3, 'HP', order=1, tau=1e-6)\n", + "seq1.setChannelFilterCompensation(3, \"HP\", order=1, tau=1e-6)\n", "\n", "# Note that setting the filter compensation may invalidate the sequence in the sense that the specified voltage ranges\n", "# on the AWG may have become too small. The function outputForAWGFile will warn you if this is the case.\n", @@ -985,7 +999,7 @@ } ], "source": [ - "# For sanity checking, it may be helpful to see how the compensated waveforms look. \n", + "# For sanity checking, it may be helpful to see how the compensated waveforms look.\n", "# The plotter function can display the delays and filter compensations\n", "\n", "plotter(seq1, apply_filters=True, apply_delays=True)" @@ -1057,8 +1071,8 @@ "# First, we make a basic element, in this example containing just a single blueprint\n", "basebp = bb.BluePrint()\n", "basebp.insertSegment(0, ramp, (0, 0), dur=0.5)\n", - "basebp.insertSegment(1, ramp, (1, 1), dur=1, name='varyme')\n", - "basebp.insertSegment(2, 'waituntil', 5)\n", + "basebp.insertSegment(1, ramp, (1, 1), dur=1, name=\"varyme\")\n", + "basebp.insertSegment(2, \"waituntil\", 5)\n", "basebp.setSR(100)\n", "\n", "baseelem = bb.Element()\n", @@ -1068,9 +1082,9 @@ "\n", "# Now we make a 5-step sequence varying the duration of the high level\n", "# The inputs are lists, since several things can be varied (see Example 2)\n", - "channels = [1] \n", - "names = ['varyme']\n", - "args = ['duration']\n", + "channels = [1]\n", + "names = [\"varyme\"]\n", + "args = [\"duration\"]\n", "iters = [[1, 1.5, 2, 2.5, 3]]\n", "\n", "seq1 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)\n", @@ -1113,8 +1127,8 @@ "# We make the same base element as in Example 1\n", "basebp = bb.BluePrint()\n", "basebp.insertSegment(0, ramp, (0, 0), dur=0.5)\n", - "basebp.insertSegment(1, ramp, (1, 1), dur=1, name='varyme')\n", - "basebp.insertSegment(2, 'waituntil', 5)\n", + "basebp.insertSegment(1, ramp, (1, 1), dur=1, name=\"varyme\")\n", + "basebp.insertSegment(2, \"waituntil\", 5)\n", "basebp.setSR(100)\n", "\n", "baseelem = bb.Element()\n", @@ -1122,9 +1136,9 @@ "\n", "# Now we make a 5-step sequence varying the duration AND the high level\n", "# We thus vary 3 things, a duration, a ramp start, and a ramp stop\n", - "channels = [1, 1, 1] \n", - "names = ['varyme', 'varyme', 'varyme']\n", - "args = ['duration', 'start', 'stop']\n", + "channels = [1, 1, 1]\n", + "names = [\"varyme\", \"varyme\", \"varyme\"]\n", + "args = [\"duration\", \"start\", \"stop\"]\n", "iters = [[1, 1.5, 2, 2.5, 3], [1, 0.8, 0.7, 0.6, 0.5], [1, 0.8, 0.7, 0.6, 0.5]]\n", "\n", "seq2 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)\n", @@ -1200,12 +1214,12 @@ "source": [ "# Example 3: Modify the high level of a square pulse inside a sequence\n", "\n", - "# \n", + "#\n", "\n", "pulsebp = bb.BluePrint()\n", "pulsebp.insertSegment(0, ramp, (0, 0), dur=5e-4)\n", - "pulsebp.insertSegment(1, ramp, (1, 1), dur=1e-3, name='varyme')\n", - "pulsebp.insertSegment(2, 'waituntil', 2e-3)\n", + "pulsebp.insertSegment(1, ramp, (1, 1), dur=1e-3, name=\"varyme\")\n", + "pulsebp.insertSegment(2, \"waituntil\", 2e-3)\n", "pulsebp.setSR(1e6)\n", "\n", "sinebp = bb.BluePrint()\n", @@ -1232,8 +1246,8 @@ "\n", "poss = [1, 1]\n", "channels = [1, 1]\n", - "names = ['varyme', 'varyme']\n", - "args = ['start', 'stop']\n", + "names = [\"varyme\", \"varyme\"]\n", + "args = [\"start\", \"stop\"]\n", "iters = [[1, 0.75, 0.5], [1, 0.75, 0.5]]\n", "\n", "newseq = bb.repeatAndVarySequence(baseseq, poss, channels, names, args, iters)\n", diff --git a/docs/examples/Subsequences.ipynb b/docs/examples/Subsequences.ipynb index a8ad1b9d9..2d916b8ca 100644 --- a/docs/examples/Subsequences.ipynb +++ b/docs/examples/Subsequences.ipynb @@ -59,7 +59,7 @@ "\n", "bp1 = bb.BluePrint()\n", "bp1.insertSegment(0, ramp, (0, 0), dur=t1)\n", - "bp1.insertSegment(1, ramp, (1, 1), dur=t2, name='perturbation')\n", + "bp1.insertSegment(1, ramp, (1, 1), dur=t2, name=\"perturbation\")\n", "bp1.insertSegment(2, ramp, (0, 0), dur=t3)\n", "bp1.setSR(SR)" ] @@ -875,12 +875,12 @@ "elem1.addBluePrint(1, bp1)\n", "\n", "elem2 = elem1.copy()\n", - "elem2.changeArg(1, 'perturbation', 'start', 0.75)\n", - "elem2.changeArg(1, 'perturbation', 'stop', 0.75)\n", + "elem2.changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "elem2.changeArg(1, \"perturbation\", \"stop\", 0.75)\n", "\n", "elem3 = elem1.copy()\n", - "elem3.changeArg(1, 'perturbation', 'start', 0.5)\n", - "elem3.changeArg(1, 'perturbation', 'stop', 0.5)\n", + "elem3.changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "elem3.changeArg(1, \"perturbation\", \"stop\", 0.5)\n", "\n", "# And put that together in a sequence\n", "seq = bb.Sequence()\n", @@ -2523,19 +2523,19 @@ "compression = 100 # this number has to be chosen with some care\n", "\n", "bp1 = bb.BluePrint()\n", - "bp1.insertSegment(0, ramp, (0, 0), dur=t1/compression)\n", + "bp1.insertSegment(0, ramp, (0, 0), dur=t1 / compression)\n", "bp1.setSR(SR)\n", "elem1 = bb.Element()\n", "elem1.addBluePrint(1, bp1)\n", "#\n", "bp2 = bb.BluePrint()\n", - "bp2.insertSegment(0, ramp, (1, 1), dur=t2, name='perturbation')\n", + "bp2.insertSegment(0, ramp, (1, 1), dur=t2, name=\"perturbation\")\n", "bp2.setSR(SR)\n", "elem2 = bb.Element()\n", "elem2.addBluePrint(1, bp2)\n", "#\n", "bp3 = bb.BluePrint()\n", - "bp3.insertSegment(0, ramp, (0, 0), dur=t3/compression)\n", + "bp3.insertSegment(0, ramp, (0, 0), dur=t3 / compression)\n", "bp3.setSR(SR)\n", "elem3 = bb.Element()\n", "elem3.addBluePrint(1, bp3)\n", @@ -2550,12 +2550,12 @@ "\n", "# Now make the variation\n", "seq2 = seq.copy()\n", - "seq2.element(2).changeArg(1, 'perturbation', 'start', 0.75)\n", - "seq2.element(2).changeArg(1, 'perturbation', 'stop', 0.75)\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"stop\", 0.75)\n", "#\n", "seq3 = seq.copy()\n", - "seq3.element(2).changeArg(1, 'perturbation', 'start', 0.5)\n", - "seq3.element(2).changeArg(1, 'perturbation', 'stop', 0.5)\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"stop\", 0.5)\n", "#\n", "fullseq = seq + seq2 + seq3\n", "plotter(fullseq)" diff --git a/src/broadbean/blueprint.py b/src/broadbean/blueprint.py index 4677f1ae7..c2efd1f9a 100644 --- a/src/broadbean/blueprint.py +++ b/src/broadbean/blueprint.py @@ -20,9 +20,18 @@ class BluePrint: The class of a waveform to become. """ - def __init__(self, funlist=None, argslist=None, namelist=None, - marker1=None, marker2=None, segmentmarker1=None, - segmentmarker2=None, SR=None, durslist=None): + def __init__( + self, + funlist=None, + argslist=None, + namelist=None, + marker1=None, + marker2=None, + segmentmarker1=None, + segmentmarker2=None, + SR=None, + durslist=None, + ): """ Create a BluePrint instance @@ -60,12 +69,13 @@ def __init__(self, funlist=None, argslist=None, namelist=None, # Are the names valid names? for name in namelist: if not isinstance(name, str): - raise ValueError('All segment names must be strings. ' - f'Received {name}.') - if name != '' and name[-1].isdigit(): - raise ValueError('Segment names are not allowed to end' - f' in a number. {name} is ' - 'therefore not a valid name.') + raise ValueError(f"All segment names must be strings. Received {name}.") + if name != "" and name[-1].isdigit(): + raise ValueError( + "Segment names are not allowed to end" + f" in a number. {name} is " + "therefore not a valid name." + ) self._funlist = funlist @@ -75,7 +85,7 @@ def __init__(self, funlist=None, argslist=None, namelist=None, for ii, name in enumerate(namelist): if isinstance(funlist[ii], str): namelist[ii] = funlist[ii] - elif name == '': + elif name == "": namelist[ii] = funlist[ii].__name__ # Allow single arguments to be given as not tuples @@ -97,11 +107,11 @@ def __init__(self, funlist=None, argslist=None, namelist=None, else: self.marker2 = marker2 if segmentmarker1 is None: - self._segmark1 = [(0, 0)]*len(funlist) + self._segmark1 = [(0, 0)] * len(funlist) else: self._segmark1 = segmentmarker1 if segmentmarker2 is None: - self._segmark2 = [(0, 0)]*len(funlist) + self._segmark2 = [(0, 0)] * len(funlist) else: self._segmark2 = segmentmarker2 @@ -123,9 +133,9 @@ def _basename(string): f"_basename received a non-string input! Got the following: {string}" ) - if string == '': + if string == "": return string - if not(string[-1].isdigit()): + if not (string[-1].isdigit()): return string else: counter = 0 @@ -180,18 +190,19 @@ def duration(self): The total duration of the BluePrint. If necessary, all the arrays are built. """ - waits = 'waituntil' in self._funlist - ensavgs = 'ensureaverage_fixed_level' in self._funlist + waits = "waituntil" in self._funlist + ensavgs = "ensureaverage_fixed_level" in self._funlist - if (not(waits) and not(ensavgs)): + if not (waits) and not (ensavgs): return sum(self._durslist) - elif (waits and not(ensavgs)): + elif waits and not (ensavgs): waitdurations = self._makeWaitDurations() return sum(waitdurations) elif ensavgs: # TODO: call the forger - raise NotImplementedError('ensureaverage_fixed_level does not' - ' exist yet. Cannot proceed') + raise NotImplementedError( + "ensureaverage_fixed_level does not exist yet. Cannot proceed" + ) @property def points(self): @@ -199,23 +210,25 @@ def points(self): The total number of points in the BluePrint. If necessary, all the arrays are built. """ - waits = 'waituntil' in self._funlist - ensavgs = 'ensureaverage_fixed_level' in self._funlist + waits = "waituntil" in self._funlist + ensavgs = "ensureaverage_fixed_level" in self._funlist SR = self.SR if SR is None: - raise ValueError('No sample rate specified, can not ' - 'return the number of points.') + raise ValueError( + "No sample rate specified, can not return the number of points." + ) - if (not(waits) and not(ensavgs)): - return int(np.round(sum(self._durslist)*SR)) - elif (waits and not(ensavgs)): + if not (waits) and not (ensavgs): + return int(np.round(sum(self._durslist) * SR)) + elif waits and not (ensavgs): waitdurations = self._makeWaitDurations() - return int(np.round(sum(waitdurations)*SR)) + return int(np.round(sum(waitdurations) * SR)) elif ensavgs: # TODO: call the forger - raise NotImplementedError('ensureaverage_fixed_level does not' - ' exist yet. Cannot proceed') + raise NotImplementedError( + "ensureaverage_fixed_level does not exist yet. Cannot proceed" + ) @property def durations(self): @@ -243,25 +256,26 @@ def description(self): for sn in range(no_segs): segkey = f"segment_{sn+1:02d}" desc[segkey] = {} - desc[segkey]['name'] = self._namelist[sn] - if self._funlist[sn] == 'waituntil': - desc[segkey]['function'] = self._funlist[sn] + desc[segkey]["name"] = self._namelist[sn] + if self._funlist[sn] == "waituntil": + desc[segkey]["function"] = self._funlist[sn] else: funname = str(self._funlist[sn])[1:] - funname = funname[:funname.find(' at')] - desc[segkey]['function'] = funname - desc[segkey]['durations'] = self._durslist[sn] - if desc[segkey]['function'] == 'waituntil': - desc[segkey]['arguments'] = {'waittime': self._argslist[sn]} + funname = funname[: funname.find(" at")] + desc[segkey]["function"] = funname + desc[segkey]["durations"] = self._durslist[sn] + if desc[segkey]["function"] == "waituntil": + desc[segkey]["arguments"] = {"waittime": self._argslist[sn]} else: sig = signature(self._funlist[sn]) - desc[segkey]['arguments'] = dict(zip(sig.parameters, - self._argslist[sn])) + desc[segkey]["arguments"] = dict( + zip(sig.parameters, self._argslist[sn]) + ) - desc['marker1_abs'] = self.marker1 - desc['marker2_abs'] = self.marker2 - desc['marker1_rel'] = self._segmark1 - desc['marker2_rel'] = self._segmark2 + desc["marker1_abs"] = self.marker1 + desc["marker2_abs"] = self.marker2 + desc["marker1_rel"] = self._segmark1 + desc["marker2_rel"] = self._segmark2 return desc @@ -273,7 +287,7 @@ def write_to_json(self, path_to_file: str) -> None: path_to_file: the path to the file to write to ex: path_to_file/blueprint.json """ - with open(path_to_file, 'w') as fp: + with open(path_to_file, "w") as fp: json.dump(self.description, fp, indent=4) @classmethod @@ -291,30 +305,35 @@ def blueprint_from_description(cls, blue_dict): if "__" not in fun } seg_mar_list = list(blue_dict.keys()) - seg_list = [s for s in seg_mar_list if 'segment' in s] + seg_list = [s for s in seg_mar_list if "segment" in s] bp_sum = cls() for i, seg in enumerate(seg_list): seg_dict = blue_dict[seg] bp_seg = BluePrint() - if seg_dict['function'] == 'waituntil': - arguments = blue_dict[seg]['arguments'].values() + if seg_dict["function"] == "waituntil": + arguments = blue_dict[seg]["arguments"].values() arguments = (list(arguments)[0][0],) - bp_seg.insertSegment(i, 'waituntil', arguments) + bp_seg.insertSegment(i, "waituntil", arguments) else: - arguments = tuple(blue_dict[seg]['arguments'].values()) - bp_seg.insertSegment(i, knowfunctions[seg_dict['function']], - arguments, name=re.sub(r'\d', "", seg_dict['name']), dur=seg_dict['durations']) + arguments = tuple(blue_dict[seg]["arguments"].values()) + bp_seg.insertSegment( + i, + knowfunctions[seg_dict["function"]], + arguments, + name=re.sub(r"\d", "", seg_dict["name"]), + dur=seg_dict["durations"], + ) bp_sum = bp_sum + bp_seg - bp_sum.marker1 = blue_dict['marker1_abs'] - bp_sum.marker2 = blue_dict['marker2_abs'] - listmarker1 = blue_dict['marker1_rel'] - listmarker2 = blue_dict['marker2_rel'] + bp_sum.marker1 = blue_dict["marker1_abs"] + bp_sum.marker2 = blue_dict["marker2_abs"] + listmarker1 = blue_dict["marker1_rel"] + listmarker2 = blue_dict["marker2_rel"] bp_sum._segmark1 = [tuple(mark) for mark in listmarker1] bp_sum._segmark2 = [tuple(mark) for mark in listmarker2] return bp_sum @classmethod - def init_from_json(cls, path_to_file: str) -> 'BluePrint': + def init_from_json(cls, path_to_file: str) -> "BluePrint": """ Reads blueprint from JSON file @@ -334,18 +353,19 @@ def _makeWaitDurations(self): Translate waituntills into durations and return that list. """ - if 'ensureaverage_fixed_level' in self._funlist: - raise NotImplementedError('There is an "ensureaverage_fixed_level"' - ' in this BluePrint. Cannot compute.') + if "ensureaverage_fixed_level" in self._funlist: + raise NotImplementedError( + 'There is an "ensureaverage_fixed_level"' + " in this BluePrint. Cannot compute." + ) funlist = self._funlist.copy() durations = self._durslist.copy() argslist = self._argslist - no_of_waits = funlist.count('waituntil') + no_of_waits = funlist.count("waituntil") - waitpositions = [ii for ii, el in enumerate(funlist) - if el == 'waituntil'] + waitpositions = [ii for ii, el in enumerate(funlist) if el == "waituntil"] # Calculate elapsed times @@ -373,27 +393,26 @@ def showPrint(self): # TODO: tidy up this method and make it use the description property if self._durslist is None: - dl = [None]*len(self._namelist) + dl = [None] * len(self._namelist) else: dl = self._durslist - datalists = [self._namelist, self._funlist, self._argslist, - dl] + datalists = [self._namelist, self._funlist, self._argslist, dl] lzip = zip(*datalists) - print('Legend: Name, function, arguments, timesteps, durations') + print("Legend: Name, function, arguments, timesteps, durations") for ind, (name, fun, args, dur) in enumerate(lzip): - ind_p = ind+1 - if fun == 'waituntil': + ind_p = ind + 1 + if fun == "waituntil": fun_p = fun else: - fun_p = fun.__str__().split(' ')[1] + fun_p = fun.__str__().split(" ")[1] list_p = [ind_p, name, fun_p, args, dur] print('Segment {}: "{}", {}, {}, {}'.format(*list_p)) - print('-'*10) + print("-" * 10) def changeArg(self, name, arg, value, replaceeverywhere=False): """ @@ -433,7 +452,6 @@ def changeArg(self, name, arg, value, replaceeverywhere=False): ) for name in replacelist: - position = self._namelist.index(name) function = self._funlist[position] sig = signature(function) @@ -447,7 +465,7 @@ def changeArg(self, name, arg, value, replaceeverywhere=False): f"{sig.parameters.keys()}." ) # Each function has two 'secret' arguments, SR and dur - user_params = len(sig.parameters)-2 + user_params = len(sig.parameters) - 2 if isinstance(arg, int) and (arg not in range(user_params)): raise ValueError( f"No argument {arg} " @@ -518,13 +536,13 @@ def changeDuration(self, name, dur, replaceeverywhere=False): position = self._namelist.index(name) if dur <= 0: - raise ValueError('Duration must be strictly greater ' - 'than zero.') + raise ValueError("Duration must be strictly greater than zero.") if self.SR is not None: - if dur*self.SR < 1: - raise ValueError('Duration too short! Must be at' - ' least 1/sample rate.') + if dur * self.SR < 1: + raise ValueError( + "Duration too short! Must be at least 1/sample rate." + ) self._durslist[position] = dur @@ -584,18 +602,19 @@ def copy(self): # Needed because of input validation in __init__ namelist = [self._basename(name) for name in self._namelist.copy()] - return BluePrint(self._funlist.copy(), - self._argslist.copy(), - namelist, - self.marker1.copy(), - self.marker2.copy(), - self._segmark1.copy(), - self._segmark2.copy(), - self._SR, - self._durslist) + return BluePrint( + self._funlist.copy(), + self._argslist.copy(), + namelist, + self.marker1.copy(), + self.marker2.copy(), + self._segmark1.copy(), + self._segmark2.copy(), + self._SR, + self._durslist, + ) - def insertSegment(self, pos, func, args=(), dur=None, name=None, - durs=None): + def insertSegment(self, pos, func, args=(), dur=None, name=None, durs=None): """ Insert a segment into the bluePrint. @@ -620,15 +639,20 @@ def insertSegment(self, pos, func, args=(), dur=None, name=None, """ # Validation - has_ensureavg = ('ensureaverage_fixed_level' in self._funlist or - 'ensureaverage_fixed_dur' in self._funlist) - if func == 'ensureaverage_fixed_level' and has_ensureavg: - raise ValueError('Can not have more than one "ensureaverage"' - ' segment in a blueprint.') + has_ensureavg = ( + "ensureaverage_fixed_level" in self._funlist + or "ensureaverage_fixed_dur" in self._funlist + ) + if func == "ensureaverage_fixed_level" and has_ensureavg: + raise ValueError( + 'Can not have more than one "ensureaverage" segment in a blueprint.' + ) if durs is not None: - warnings.warn('Deprecation warning: please specify "dur" rather ' - 'than "durs" when inserting a segment') + warnings.warn( + 'Deprecation warning: please specify "dur" rather ' + 'than "durs" when inserting a segment' + ) if dur is None: dur = durs else: @@ -640,17 +664,17 @@ def insertSegment(self, pos, func, args=(), dur=None, name=None, args = (args,) if pos < -1: - raise ValueError('Position must be strictly larger than -1') + raise ValueError("Position must be strictly larger than -1") - if name is None or name == '': - if func == 'waituntil': - name = 'waituntil' + if name is None or name == "": + if func == "waituntil": + name = "waituntil" else: name = func.__name__ elif isinstance(name, str): if len(name) > 0: if name[-1].isdigit(): - raise ValueError('Segment name must not end in a number') + raise ValueError("Segment name must not end in a number") if pos == -1: self._namelist.append(name) @@ -800,10 +824,10 @@ def _subelementBuilder( durations = durs.copy() - no_of_waits = funlist.count('waituntil') + no_of_waits = funlist.count("waituntil") # handle waituntil by translating it into a normal function - waitpositions = [ii for ii, el in enumerate(funlist) if el == 'waituntil'] + waitpositions = [ii for ii, el in enumerate(funlist) if el == "waituntil"] # Calculate elapsed times @@ -838,7 +862,7 @@ def _subelementBuilder( intdurations = np.zeros(len(newdurations), dtype=int) for ii, dur in enumerate(newdurations): - int_dur = round(dur*SR) + int_dur = round(dur * SR) if int_dur < 2: raise SegmentDurationError( "Too short segment detected! " @@ -851,13 +875,15 @@ def _subelementBuilder( ) else: intdurations[ii] = int_dur - newdurations[ii] = int_dur/SR + newdurations[ii] = int_dur / SR # The actual forging of the waveform wf_length = np.sum(intdurations) parts = [ft.partial(fun, *args) for (fun, args) in zip(funlist, argslist)] blocks = [p(SR, d) for (p, d) in zip(parts, intdurations)] - output = np.fromiter((block for sl in blocks for block in sl), float, count=wf_length) + output = np.fromiter( + (block for sl in blocks for block in sl), float, count=wf_length + ) # now make the markers time = np.linspace(0, sum(newdurations), wf_length, endpoint=False) @@ -879,12 +905,17 @@ def _subelementBuilder( msettings = [marker1, marker2] marks = [m1, m2] for marker, setting in zip(marks, msettings): - for (t, dur) in setting: - ind = np.abs(time-t).argmin() - chunk = int(np.round(dur*SR)) - marker[ind:ind+chunk] = 1 - - outdict = {'wfm': output, 'm1': m1, 'm2': m2, 'time': time, - 'newdurations': newdurations} + for t, dur in setting: + ind = np.abs(time - t).argmin() + chunk = int(np.round(dur * SR)) + marker[ind : ind + chunk] = 1 + + outdict = { + "wfm": output, + "m1": m1, + "m2": m2, + "time": time, + "newdurations": newdurations, + } return outdict diff --git a/src/broadbean/element.py b/src/broadbean/element.py index 61542228f..123601d8b 100644 --- a/src/broadbean/element.py +++ b/src/broadbean/element.py @@ -25,7 +25,6 @@ class Element: """ def __init__(self): - # The internal data structure, a dict with key channel number # Each value is a dict with the following possible keys, values: # 'blueprint': a BluePrint @@ -41,25 +40,30 @@ def __init__(self): self._data = {} self._meta = {} - def addBluePrint(self, channel: Union[str, int], - blueprint: BluePrint) -> None: + def addBluePrint(self, channel: Union[str, int], blueprint: BluePrint) -> None: """ Add a blueprint to the element on the specified channel. Overwrites whatever was there before. """ if not isinstance(blueprint, BluePrint): - raise ValueError('Invalid blueprint given. Must be an instance' - ' of the BluePrint class.') + raise ValueError( + "Invalid blueprint given. Must be an instance" + " of the BluePrint class." + ) - if [] in [blueprint._funlist, blueprint._argslist, blueprint._namelist, - blueprint._durslist]: - raise ValueError('Received empty BluePrint. Can not proceed.') + if [] in [ + blueprint._funlist, + blueprint._argslist, + blueprint._namelist, + blueprint._durslist, + ]: + raise ValueError("Received empty BluePrint. Can not proceed.") # important: make a copy of the blueprint newprint = blueprint.copy() self._data[channel] = {} - self._data[channel]['blueprint'] = newprint + self._data[channel]["blueprint"] = newprint def addFlags( self, channel: Union[str, int], flags: Sequence[Union[str, int]] @@ -102,8 +106,9 @@ def addFlags( self._data[channel]["flags"] = flags_int - def addArray(self, channel: Union[int, str], waveform: np.ndarray, - SR: int, **kwargs) -> None: + def addArray( + self, channel: Union[int, str], waveform: np.ndarray, SR: int, **kwargs + ) -> None: """ Add an array of voltage value to the element on the specified channel. Overwrites whatever was there before. Markers can be specified via @@ -118,16 +123,18 @@ def addArray(self, channel: Union[int, str], waveform: np.ndarray, N = len(waveform) self._data[channel] = {} - self._data[channel]['array'] = {} + self._data[channel]["array"] = {} for name, array in kwargs.items(): if len(array) != N: - raise ValueError('Length mismatch between waveform and ' - f'array {name}. Must be same length') - self._data[channel]['array'].update({name: array}) + raise ValueError( + "Length mismatch between waveform and " + f"array {name}. Must be same length" + ) + self._data[channel]["array"].update({name: array}) - self._data[channel]['array']['wfm'] = waveform - self._data[channel]['SR'] = SR + self._data[channel]["array"]["wfm"] = waveform + self._data[channel]["SR"] = SR def validateDurations(self): """ @@ -139,15 +146,15 @@ def validateDurations(self): channels = self._data.values() if len(channels) == 0: - raise KeyError('Empty Element, nothing assigned') + raise KeyError("Empty Element, nothing assigned") # First the sample rate SRs = [] for channel in channels: - if 'blueprint' in channel.keys(): - SRs.append(channel['blueprint'].SR) - elif 'array' in channel.keys(): - SR = channel['SR'] + if "blueprint" in channel.keys(): + SRs.append(channel["blueprint"].SR) + elif "array" in channel.keys(): + SR = channel["SR"] SRs.append(SR) if not SRs.count(SRs[0]) == len(SRs): @@ -161,10 +168,10 @@ def validateDurations(self): # Next the total time durations = [] for channel in channels: - if 'blueprint' in channel.keys(): - durations.append(channel['blueprint'].duration) - elif 'array' in channel.keys(): - length = len(channel['array']['wfm'])/channel['SR'] + if "blueprint" in channel.keys(): + durations.append(channel["blueprint"].duration) + elif "array" in channel.keys(): + length = len(channel["array"]["wfm"]) / channel["SR"] durations.append(length) if None not in SRs: @@ -184,10 +191,10 @@ def validateDurations(self): # (kind of redundant if sample rate and duration match?) npts = [] for channel in channels: - if 'blueprint' in channel.keys(): - npts.append(channel['blueprint'].points) - elif 'array' in channel.keys(): - length = len(channel['array']['wfm']) + if "blueprint" in channel.keys(): + npts.append(channel["blueprint"].points) + elif "array" in channel.keys(): + length = len(channel["array"]["wfm"]) npts.append(length) if not npts.count(npts[0]) == len(npts): @@ -200,8 +207,8 @@ def validateDurations(self): # If these three tests pass, we equip the dictionary with convenient # info used by Sequence - self._meta['SR'] = SRs[0] - self._meta['duration'] = durations[0] + self._meta["SR"] = SRs[0] + self._meta["duration"] = durations[0] def getArrays(self, includetime: bool = False) -> dict[int, dict[str, np.ndarray]]: """ @@ -237,8 +244,8 @@ def getArrays(self, includetime: bool = False) -> dict[int, dict[str, np.ndarray if "flags" in signal.keys(): outdict[channel]["flags"] = signal["flags"] if not includetime: - outdict[channel].pop('time') - outdict[channel].pop('newdurations') + outdict[channel].pop("time") + outdict[channel].pop("newdurations") # TODO: should the be a separate bool for newdurations? return outdict @@ -252,7 +259,7 @@ def SR(self): # Will either raise an error or set self._data['SR'] self.validateDurations() - return self._meta['SR'] + return self._meta["SR"] @property def points(self) -> int: @@ -268,7 +275,6 @@ def points(self) -> int: # if validateDurations did not raise an error, all channels # have the same number of points for chan in channels: - if not ("array" in chan.keys() or "blueprint" in chan.keys()): raise ValueError( f"Neither BluePrint nor array assigned to chan {chan}!" @@ -276,12 +282,12 @@ def points(self) -> int: if "blueprint" in chan.keys(): return chan["blueprint"].points else: - return len(chan['array']['wfm']) + return len(chan["array"]["wfm"]) else: # this line is here to make mypy happy; this exception is # already raised by validateDurations - raise KeyError('Empty Element, nothing assigned') + raise KeyError("Empty Element, nothing assigned") @property def duration(self): @@ -292,7 +298,7 @@ def duration(self): # Will either raise an error or set self._data['SR'] self.validateDurations() - return self._meta['duration'] + return self._meta["duration"] @property def channels(self): @@ -310,10 +316,10 @@ def description(self): desc = {} for key, val in self._data.items(): - if 'blueprint' in val.keys(): - desc[str(key)] = val['blueprint'].description - elif 'array' in val.keys(): - desc[str(key)] = 'array' + if "blueprint" in val.keys(): + desc[str(key)] = val["blueprint"].description + elif "array" in val.keys(): + desc[str(key)] = "array" if "flags" in val.keys(): desc[str(key)]["flags"] = val["flags"] @@ -328,7 +334,7 @@ def write_to_json(self, path_to_file: str) -> None: path_to_file: the path to the file to write to ex: path_to_file/element.json """ - with open(path_to_file, 'w') as fp: + with open(path_to_file, "w") as fp: json.dump(self.description, fp, indent=4) @classmethod @@ -363,9 +369,14 @@ def init_from_json(cls, path_to_file: str) -> Element: data_loaded = json.load(fp) return cls.element_from_description(data_loaded) - def changeArg(self, channel: Union[str, int], - name: str, arg: Union[str, int], value: Union[int, float], - replaceeverywhere: bool=False) -> None: + def changeArg( + self, + channel: Union[str, int], + name: str, + arg: Union[str, int], + value: Union[int, float], + replaceeverywhere: bool = False, + ) -> None: """ Change the argument of a function of the blueprint on the specified channel. @@ -388,18 +399,22 @@ def changeArg(self, channel: Union[str, int], """ if channel not in self.channels: - raise ValueError(f'Nothing assigned to channel {channel}') + raise ValueError(f"Nothing assigned to channel {channel}") if "blueprint" not in self._data[channel].keys(): raise ValueError(f"No blueprint on channel {channel}.") - bp = self._data[channel]['blueprint'] + bp = self._data[channel]["blueprint"] bp.changeArg(name, arg, value, replaceeverywhere) - def changeDuration(self, channel: Union[str, int], name: str, - newdur: Union[int, float], - replaceeverywhere: bool=False) -> None: + def changeDuration( + self, + channel: Union[str, int], + name: str, + newdur: Union[int, float], + replaceeverywhere: bool = False, + ) -> None: """ Change the duration of a segment of the blueprint on the specified channel @@ -416,12 +431,12 @@ def changeDuration(self, channel: Union[str, int], name: str, """ if channel not in self.channels: - raise ValueError(f'Nothing assigned to channel {channel}') + raise ValueError(f"Nothing assigned to channel {channel}") if "blueprint" not in self._data[channel].keys(): raise ValueError(f"No blueprint on channel {channel}.") - bp = self._data[channel]['blueprint'] + bp = self._data[channel]["blueprint"] bp.changeDuration(name, newdur, replaceeverywhere) @@ -438,11 +453,13 @@ def _applyDelays(self, delays: list[float]) -> None: 1 by 1 ms and channel 3 by nothing. """ if len(delays) != len(self.channels): - raise ValueError('Incorrect number of delays specified.' - ' Must match the number of channels.') + raise ValueError( + "Incorrect number of delays specified." + " Must match the number of channels." + ) if not sum(d >= 0 for d in delays) == len(delays): - raise ValueError('Negative delays not allowed.') + raise ValueError("Negative delays not allowed.") # The strategy is: # Add waituntil at the beginning, update all waituntils inside, add a @@ -455,28 +472,28 @@ def _applyDelays(self, delays: list[float]) -> None: for chanind, chan in enumerate(self.channels): delay = delays[chanind] - if 'blueprint' in self._data[chan].keys(): - blueprint = self._data[chan]['blueprint'] + if "blueprint" in self._data[chan].keys(): + blueprint = self._data[chan]["blueprint"] # update existing waituntils for segpos in range(len(blueprint._funlist)): - if blueprint._funlist[segpos] == 'waituntil': + if blueprint._funlist[segpos] == "waituntil": oldwait = blueprint._argslist[segpos][0] - blueprint._argslist[segpos] = (oldwait+delay,) + blueprint._argslist[segpos] = (oldwait + delay,) # insert delay before the waveform if delay > 0: - blueprint.insertSegment(0, 'waituntil', (delay,), - 'waituntil') + blueprint.insertSegment(0, "waituntil", (delay,), "waituntil") # add zeros at the end - if maxdelay-delay > 0: - blueprint.insertSegment(-1, PulseAtoms.ramp, (0, 0), - dur=maxdelay-delay) + if maxdelay - delay > 0: + blueprint.insertSegment( + -1, PulseAtoms.ramp, (0, 0), dur=maxdelay - delay + ) else: - arrays = self._data[chan]['array'] + arrays = self._data[chan]["array"] for name, arr in arrays.items(): - pre_wait = np.zeros(int(delay*SR)) - post_wait = np.zeros(int((maxdelay-delay)*SR)) + pre_wait = np.zeros(int(delay * SR)) + post_wait = np.zeros(int((maxdelay - delay) * SR)) arrays[name] = np.concatenate((pre_wait, arr, post_wait)) def copy(self): diff --git a/src/broadbean/plotting.py b/src/broadbean/plotting.py index e6bb80e20..3e09c72b5 100644 --- a/src/broadbean/plotting.py +++ b/src/broadbean/plotting.py @@ -29,17 +29,17 @@ def getSIScalingAndPrefix(minmax: tuple[float, float]) -> tuple[float, str]: if v_max == 0: v_max = 1 exponent = np.log10(v_max) - prefix = '' + prefix = "" scaling: float = 1 if exponent < 0: - prefix = 'm' + prefix = "m" scaling = 1e3 if exponent < -3: - prefix = 'micro ' + prefix = "micro " scaling = 1e6 if exponent < -6: - prefix = 'n' + prefix = "n" scaling = 1e9 return (scaling, prefix) @@ -77,7 +77,7 @@ def _plot_object_forger(obj_to_plot: BBObject, **forger_kwargs) -> dict[int, dic elif isinstance(obj_to_plot, Element): seq = Sequence() seq.addElement(1, obj_to_plot) - seq.setSR(obj_to_plot._meta['SR']) + seq.setSR(obj_to_plot._meta["SR"]) elif isinstance(obj_to_plot, Sequence): seq = obj_to_plot @@ -88,41 +88,42 @@ def _plot_object_forger(obj_to_plot: BBObject, **forger_kwargs) -> dict[int, dic def _plot_summariser(seq: dict[int, dict]) -> dict[int, dict[str, np.ndarray]]: - """ - Return a plotting summary of a subsequence. - - Args: - seq: The 'content' value of a forged sequence where a - subsequence resides + """ + Return a plotting summary of a subsequence. - Returns: - A dict that looks like a forged element, but all waveforms - are just two points, np.array([min, max]) - """ + Args: + seq: The 'content' value of a forged sequence where a + subsequence resides - output = {} + Returns: + A dict that looks like a forged element, but all waveforms + are just two points, np.array([min, max]) + """ - # we assume correctness, all postions specify the same channels - chans = seq[1]['data'].keys() + output = {} - minmax = dict(zip(chans, [(0, 0)]*len(chans))) + # we assume correctness, all postions specify the same channels + chans = seq[1]["data"].keys() - for element in seq.values(): + minmax = dict(zip(chans, [(0, 0)] * len(chans))) - arr_dict = element['data'] + for element in seq.values(): + arr_dict = element["data"] - for chan in chans: - wfm = arr_dict[chan]['wfm'] - if wfm.min() < minmax[chan][0]: - minmax[chan] = (wfm.min(), minmax[chan][1]) - if wfm.max() > minmax[chan][1]: - minmax[chan] = (minmax[chan][0], wfm.max()) - output[chan] = {'wfm': np.array(minmax[chan]), - 'm1': np.zeros(2), - 'm2': np.zeros(2), - 'time': np.linspace(0, 1, 2)} + for chan in chans: + wfm = arr_dict[chan]["wfm"] + if wfm.min() < minmax[chan][0]: + minmax[chan] = (wfm.min(), minmax[chan][1]) + if wfm.max() > minmax[chan][1]: + minmax[chan] = (minmax[chan][0], wfm.max()) + output[chan] = { + "wfm": np.array(minmax[chan]), + "m1": np.zeros(2), + "m2": np.zeros(2), + "time": np.linspace(0, 1, 2), + } - return output + return output # the Grand Unified Plotter @@ -144,7 +145,7 @@ def plotter(obj_to_plot: BBObject, **forger_kwargs) -> None: seq = _plot_object_forger(obj_to_plot, **forger_kwargs) # Get the dimensions. - chans = seq[1]['content'][1]['data'].keys() + chans = seq[1]["content"][1]["data"].keys() seqlen = len(seq.keys()) def update_minmax(chanminmax, wfmdata, chanind): @@ -160,30 +161,27 @@ def update_minmax(chanminmax, wfmdata, chanind): inf: float = np.inf chanminmax: list[tuple[float, float]] = [(inf, minf)] * len(chans) for chanind, chan in enumerate(chans): - for pos in range(1, seqlen+1): - if seq[pos]['type'] == 'element': - wfmdata = (seq[pos]['content'][1] - ['data'][chan]['wfm']) + for pos in range(1, seqlen + 1): + if seq[pos]["type"] == "element": + wfmdata = seq[pos]["content"][1]["data"][chan]["wfm"] chanminmax = update_minmax(chanminmax, wfmdata, chanind) - elif seq[pos]['type'] == 'subsequence': - for pos2 in seq[pos]['content'].keys(): - elem = seq[pos]['content'][pos2]['data'] - wfmdata = elem[chan]['wfm'] - chanminmax = update_minmax(chanminmax, - wfmdata, chanind) + elif seq[pos]["type"] == "subsequence": + for pos2 in seq[pos]["content"].keys(): + elem = seq[pos]["content"][pos2]["data"] + wfmdata = elem[chan]["wfm"] + chanminmax = update_minmax(chanminmax, wfmdata, chanind) fig, axs = plt.subplots(len(chans), seqlen) # ...and do the plotting for chanind, chan in enumerate(chans): - # figure out the channel voltage scaling # The entire channel shares a y-axis minmax: tuple[float, float] = chanminmax[chanind] (voltagescaling, voltageprefix) = getSIScalingAndPrefix(minmax) - voltageunit = voltageprefix + 'V' + voltageunit = voltageprefix + "V" for pos in range(seqlen): # 1 by N arrays are indexed differently than M by N arrays @@ -198,89 +196,129 @@ def update_minmax(chanminmax, wfmdata, chanind): ax = axs[chanind, pos] # reduce the tickmark density (must be called before scaling) - ax.locator_params(tight=True, nbins=4, prune='lower') + ax.locator_params(tight=True, nbins=4, prune="lower") - if seq[pos+1]['type'] == 'element': - content = seq[pos+1]['content'][1]['data'][chan] - wfm = content['wfm'] - m1 = content.get('m1', np.zeros_like(wfm)) - m2 = content.get('m2', np.zeros_like(wfm)) - time = content['time'] - newdurs = content.get('newdurations', []) + if seq[pos + 1]["type"] == "element": + content = seq[pos + 1]["content"][1]["data"][chan] + wfm = content["wfm"] + m1 = content.get("m1", np.zeros_like(wfm)) + m2 = content.get("m2", np.zeros_like(wfm)) + time = content["time"] + newdurs = content.get("newdurations", []) else: - arr_dict = _plot_summariser(seq[pos+1]['content']) - wfm = arr_dict[chan]['wfm'] + arr_dict = _plot_summariser(seq[pos + 1]["content"]) + wfm = arr_dict[chan]["wfm"] newdurs = [] - ax.annotate('SUBSEQ', xy=(0.5, 0.5), - xycoords='axes fraction', - horizontalalignment='center') + ax.annotate( + "SUBSEQ", + xy=(0.5, 0.5), + xycoords="axes fraction", + horizontalalignment="center", + ) time = np.linspace(0, 1, 2) # needed for timeexponent # Figure out the axes' scaling timeexponent = np.log10(time.max()) - timeunit = 's' + timeunit = "s" timescaling: float = 1.0 if timeexponent < 0: - timeunit = 'ms' + timeunit = "ms" timescaling = 1e3 if timeexponent < -3: - timeunit = 'micro s' + timeunit = "micro s" timescaling = 1e6 if timeexponent < -6: - timeunit = 'ns' + timeunit = "ns" timescaling = 1e9 - if seq[pos+1]['type'] == 'element': - ax.plot(timescaling*time, voltagescaling*wfm, lw=3, - color=(0.6, 0.4, 0.3), alpha=0.4) + if seq[pos + 1]["type"] == "element": + ax.plot( + timescaling * time, + voltagescaling * wfm, + lw=3, + color=(0.6, 0.4, 0.3), + alpha=0.4, + ) ymax = voltagescaling * chanminmax[chanind][1] ymin = voltagescaling * chanminmax[chanind][0] yrange = ymax - ymin - ax.set_ylim([ymin-0.05*yrange, ymax+0.2*yrange]) + ax.set_ylim([ymin - 0.05 * yrange, ymax + 0.2 * yrange]) - if seq[pos+1]['type'] == 'element': + if seq[pos + 1]["type"] == "element": # TODO: make this work for more than two markers # marker1 (red, on top) - y_m1 = ymax+0.15*yrange + y_m1 = ymax + 0.15 * yrange marker_on = np.ones_like(m1) marker_on[m1 == 0] = np.nan marker_off = np.ones_like(m1) - ax.plot(timescaling*time, y_m1*marker_off, - color=(0.6, 0.1, 0.1), alpha=0.2, lw=2) - ax.plot(timescaling*time, y_m1*marker_on, - color=(0.6, 0.1, 0.1), alpha=0.6, lw=2) + ax.plot( + timescaling * time, + y_m1 * marker_off, + color=(0.6, 0.1, 0.1), + alpha=0.2, + lw=2, + ) + ax.plot( + timescaling * time, + y_m1 * marker_on, + color=(0.6, 0.1, 0.1), + alpha=0.6, + lw=2, + ) # marker 2 (blue, below the red) - y_m2 = ymax+0.10*yrange + y_m2 = ymax + 0.10 * yrange marker_on = np.ones_like(m2) marker_on[m2 == 0] = np.nan marker_off = np.ones_like(m2) - ax.plot(timescaling*time, y_m2*marker_off, - color=(0.1, 0.1, 0.6), alpha=0.2, lw=2) - ax.plot(timescaling*time, y_m2*marker_on, - color=(0.1, 0.1, 0.6), alpha=0.6, lw=2) + ax.plot( + timescaling * time, + y_m2 * marker_off, + color=(0.1, 0.1, 0.6), + alpha=0.2, + lw=2, + ) + ax.plot( + timescaling * time, + y_m2 * marker_on, + color=(0.1, 0.1, 0.6), + alpha=0.6, + lw=2, + ) # If subsequence, plot lines indicating min and max value - if seq[pos+1]['type'] == 'subsequence': + if seq[pos + 1]["type"] == "subsequence": # min: - ax.plot(time, np.ones_like(time)*wfm[0], - color=(0.12, 0.12, 0.12), alpha=0.2, lw=2) + ax.plot( + time, + np.ones_like(time) * wfm[0], + color=(0.12, 0.12, 0.12), + alpha=0.2, + lw=2, + ) # max: - ax.plot(time, np.ones_like(time)*wfm[1], - color=(0.12, 0.12, 0.12), alpha=0.2, lw=2) + ax.plot( + time, + np.ones_like(time) * wfm[1], + color=(0.12, 0.12, 0.12), + alpha=0.2, + lw=2, + ) ax.set_xticks([]) # time step lines for dur in np.cumsum(newdurs): - ax.plot([timescaling*dur, timescaling*dur], - [ax.get_ylim()[0], ax.get_ylim()[1]], - color=(0.312, 0.2, 0.33), - alpha=0.3) + ax.plot( + [timescaling * dur, timescaling * dur], + [ax.get_ylim()[0], ax.get_ylim()[1]], + color=(0.312, 0.2, 0.33), + alpha=0.3, + ) # labels if pos == 0: @@ -289,18 +327,18 @@ def update_minmax(chanminmax, wfmdata, chanind): newax = ax.twinx() newax.set_yticks([]) if isinstance(chan, int): - new_ylabel = f'Ch. {chan}' + new_ylabel = f"Ch. {chan}" elif isinstance(chan, str): new_ylabel = chan newax.set_ylabel(new_ylabel) - if seq[pos+1]['type'] == 'subsequence': - ax.set_xlabel('Time N/A') + if seq[pos + 1]["type"] == "subsequence": + ax.set_xlabel("Time N/A") else: ax.set_xlabel(f"({timeunit})") # remove excess space from the plot - if not chanind+1 == len(chans): + if not chanind + 1 == len(chans): ax.set_xticks([]) if not pos == 0: ax.set_yticks([]) @@ -308,20 +346,20 @@ def update_minmax(chanminmax, wfmdata, chanind): # display sequencer information if chanind == 0 and isinstance(obj_to_plot, Sequence): - seq_info = seq[pos+1]['sequencing'] - titlestring = '' - if seq_info['twait'] == 1: # trigger wait - titlestring += 'T ' - if seq_info['nrep'] > 1: # nreps - titlestring += '\u21BB{} '.format(seq_info['nrep']) - if seq_info['nrep'] == 0: - titlestring += '\u221E ' - if seq_info['jump_input'] != 0: - if seq_info['jump_input'] == -1: - titlestring += 'E\u2192 ' + seq_info = seq[pos + 1]["sequencing"] + titlestring = "" + if seq_info["twait"] == 1: # trigger wait + titlestring += "T " + if seq_info["nrep"] > 1: # nreps + titlestring += "\u21bb{} ".format(seq_info["nrep"]) + if seq_info["nrep"] == 0: + titlestring += "\u221e " + if seq_info["jump_input"] != 0: + if seq_info["jump_input"] == -1: + titlestring += "E\u2192 " else: - titlestring += 'E{} '.format(seq_info['jump_input']) - if seq_info['goto'] > 0: - titlestring += '\u21b1{}'.format(seq_info['goto']) + titlestring += "E{} ".format(seq_info["jump_input"]) + if seq_info["goto"] > 0: + titlestring += "\u21b1{}".format(seq_info["goto"]) ax.set_title(titlestring) diff --git a/src/broadbean/ripasso.py b/src/broadbean/ripasso.py index 95171de46..7461ef357 100644 --- a/src/broadbean/ripasso.py +++ b/src/broadbean/ripasso.py @@ -17,19 +17,19 @@ class MissingFrequenciesError(Exception): pass -def _rcFilter(SR, npts, f_cut, kind='HP', order=1, DCgain=0): +def _rcFilter(SR, npts, f_cut, kind="HP", order=1, DCgain=0): """ Nth order (RC circuit) filter made with frequencies matching the fft output """ - freqs = fftfreq(npts, 1/SR) + freqs = fftfreq(npts, 1 / SR) - tau = 1/f_cut - top = 2j*np.pi + tau = 1 / f_cut + top = 2j * np.pi - if kind == 'HP': - tf = top*tau*freqs/(1+top*tau*freqs) + if kind == "HP": + tf = top * tau * freqs / (1 + top * tau * freqs) # now, we have identically zero gain for the DC component, # which makes the transfer function non-invertible @@ -38,8 +38,8 @@ def _rcFilter(SR, npts, f_cut, kind='HP', order=1, DCgain=0): tf[tf == 0] = DCgain # No DC suppression - elif kind == 'LP': - tf = 1/(1+top*tau*freqs) + elif kind == "LP": + tf = 1 / (1 + top * tau * freqs) return tf**order @@ -69,12 +69,12 @@ def applyRCFilter(signal, SR, kind, f_cut, order, DCgain=0): ValueError: If kind is neither 'HP' nor 'LP' """ - if kind not in ['HP', 'LP']: + if kind not in ["HP", "LP"]: raise ValueError('Please specify filter type as either "HP" or "LP".') N = len(signal) transfun = _rcFilter(SR, N, f_cut, kind=kind, order=order, DCgain=DCgain) - output = ifft(fft(signal)*transfun) + output = ifft(fft(signal) * transfun) output = np.real(output) return output @@ -109,17 +109,17 @@ def applyInverseRCFilter(signal, SR, kind, f_cut, order, DCgain=1): ValueError: If DCgain is zero. """ - if kind not in ['HP', 'LP']: - raise ValueError('Wrong filter type. ' - 'Please specify filter type as either "HP" or "LP".') + if kind not in ["HP", "LP"]: + raise ValueError( + 'Wrong filter type. Please specify filter type as either "HP" or "LP".' + ) if not DCgain > 0: - raise ValueError('Non-invertible DCgain! ' - 'Please set DCgain to a finite value.') + raise ValueError("Non-invertible DCgain! Please set DCgain to a finite value.") N = len(signal) transfun = _rcFilter(SR, N, f_cut, order=-order, kind=kind, DCgain=DCgain) - output = ifft(fft(signal)*transfun) + output = ifft(fft(signal) * transfun) output = np.real(output) return output @@ -154,25 +154,29 @@ def applyCustomTransferFunction(signal, SR, tf_freqs, tf_amp, invert=False): df = np.diff(tf_freqs).round(6) if not np.sum(df > 0) == len(df): - raise ValueError('Invalid transfer function freq. axis. ' - 'Frequencies must be monotonically increasing.') + raise ValueError( + "Invalid transfer function freq. axis. " + "Frequencies must be monotonically increasing." + ) - if not tf_freqs[-1] >= SR/2: + if not tf_freqs[-1] >= SR / 2: # TODO: think about whether this is a problem # What is the desired behaviour for high frequencies if nothing # is specified? I guess NOOP, i.e. the transfer func. is 1 - raise MissingFrequenciesError('Supplied transfer function does not ' - 'specify frequency response up to the ' - 'Nyquist frequency of the signal.') + raise MissingFrequenciesError( + "Supplied transfer function does not " + "specify frequency response up to the " + "Nyquist frequency of the signal." + ) if not tf_freqs[0] == 0: # what to do in this case? Extrapolate 1s? Make the user do this? pass # Step 1: resample to fftfreq type axis - freqax = fftfreq(npts, 1/SR) - freqax_pos = freqax[:npts//2] - freqax_neg = freqax[npts//2:] + freqax = fftfreq(npts, 1 / SR) + freqax_pos = freqax[: npts // 2] + freqax_neg = freqax[npts // 2 :] resampled_pos = np.interp(freqax_pos, tf_freqs, tf_amp) resampled_neg = np.interp(-freqax_neg[::-1], tf_freqs, tf_amp) @@ -185,7 +189,7 @@ def applyCustomTransferFunction(signal, SR, tf_freqs, tf_amp, invert=False): else: power = 1 - signal_filtered = ifft(fft(signal)*(transferfun**power)) + signal_filtered = ifft(fft(signal) * (transferfun**power)) imax = np.imag(signal_filtered).max() log.debug( "Applying custom transfer function. Discarding imag parts " diff --git a/src/broadbean/sequence.py b/src/broadbean/sequence.py index 146e11246..c08267b45 100644 --- a/src/broadbean/sequence.py +++ b/src/broadbean/sequence.py @@ -22,11 +22,20 @@ log = logging.getLogger(__name__) -fs_schema = Schema({int: {'type': Or('subsequence', 'element'), - 'content': {int: {'data': {Or(str, int): {str: np.ndarray}}, - Optional('sequencing'): {Optional(str): - int}}}, - 'sequencing': {Optional(str): int}}}) +fs_schema = Schema( + { + int: { + "type": Or("subsequence", "element"), + "content": { + int: { + "data": {Or(str, int): {str: np.ndarray}}, + Optional("sequencing"): {Optional(str): int}, + } + }, + "sequencing": {Optional(str): int}, + } + } +) class SequencingError(Exception): @@ -84,7 +93,7 @@ def __init__(self): # some backends (seqx files) allow for a sequence to have a name # we make the name a property of the sequence - self._name = '' + self._name = "" def __eq__(self, other): if not isinstance(other, Sequence): @@ -109,14 +118,14 @@ def __add__(self, other): # Validation if not self.checkConsistency(): - raise SequenceConsistencyError('Left hand sequence inconsistent!') + raise SequenceConsistencyError("Left hand sequence inconsistent!") if not other.checkConsistency(): - raise SequenceConsistencyError('Right hand sequence inconsistent!') + raise SequenceConsistencyError("Right hand sequence inconsistent!") if not self._awgspecs == other._awgspecs: - raise SequenceCompatibilityError('Incompatible sequences: ' - 'different AWG' - 'specifications.') + raise SequenceCompatibilityError( + "Incompatible sequences: different AWGspecifications." + ) newseq = Sequence() N = len(self._data) @@ -135,11 +144,11 @@ def __add__(self, other): for key, item in other._sequencing.items(): newitem = item.copy() # update goto and jump according to new sequence length - if newitem['goto'] > 0: - newitem['goto'] += N - if newitem['jump_target'] > 0: - newitem['jump_target'] += N - newsequencing2.update({key+N: newitem}) + if newitem["goto"] > 0: + newitem["goto"] += N + if newitem["jump_target"] > 0: + newitem["jump_target"] += N + newsequencing2.update({key + N: newitem}) newsequencing1.update(newsequencing2) @@ -179,9 +188,11 @@ def setSequenceSettings(self, pos, wait, nreps, jump, goto): 0 means next. """ - warnings.warn('Deprecation warning. This function is only compatible ' - 'with AWG5014 output and will be removed. ' - 'Please use the specific setSequencingXXX methods.') + warnings.warn( + "Deprecation warning. This function is only compatible " + "with AWG5014 output and will be removed. " + "Please use the specific setSequencingXXX methods." + ) # Validation (some validation 'postponed' and put in checkConsistency) # @@ -189,9 +200,13 @@ def setSequenceSettings(self, pos, wait, nreps, jump, goto): # most validation of these settings is deferred and performed # in the outputForXXX methods - self._sequencing[pos] = {'twait': wait, 'nrep': nreps, - 'jump_target': jump, 'goto': goto, - 'jump_input': 0} + self._sequencing[pos] = { + "twait": wait, + "nrep": nreps, + "jump_target": jump, + "goto": goto, + "jump_input": 0, + } def setSequencingTriggerWait(self, pos: int, wait: int) -> None: """ @@ -203,7 +218,7 @@ def setSequencingTriggerWait(self, pos: int, wait: int) -> None: pos: The sequence element (counting from 1) wait: The wait state/input depending on backend. """ - self._sequencing[pos]['twait'] = wait + self._sequencing[pos]["twait"] = wait def setSequencingNumberOfRepetitions(self, pos: int, nrep: int) -> None: """ @@ -213,7 +228,7 @@ def setSequencingNumberOfRepetitions(self, pos: int, nrep: int) -> None: pos: The sequence element (counting from 1) nrep: The number of repetitions (0 means infinite) """ - self._sequencing[pos]['nrep'] = nrep + self._sequencing[pos]["nrep"] = nrep def setSequencingEventInput(self, pos: int, jump_input: int) -> None: """ @@ -225,7 +240,7 @@ def setSequencingEventInput(self, pos: int, jump_input: int) -> None: jump_input: The input specifier, 0 for off, 1 for 'TrigA', 2 for 'TrigB', 3 for 'Internal'. """ - self._sequencing[pos]['jump_input'] = jump_input + self._sequencing[pos]["jump_input"] = jump_input def setSequencingEventJumpTarget(self, pos: int, jump_target: int) -> None: """ @@ -235,7 +250,7 @@ def setSequencingEventJumpTarget(self, pos: int, jump_target: int) -> None: pos: The sequence element (counting from 1) jump_target: The sequence element to jump to (counting from 1) """ - self._sequencing[pos]['jump_target'] = jump_target + self._sequencing[pos]["jump_target"] = jump_target def setSequencingGoto(self, pos: int, goto: int) -> None: """ @@ -246,13 +261,13 @@ def setSequencingGoto(self, pos: int, goto: int) -> None: pos: The sequence element (counting from 1) goto: The position of the element to play. 0 means 'next in line' """ - self._sequencing[pos]['goto'] = goto + self._sequencing[pos]["goto"] = goto def setSR(self, SR): """ Set the sample rate for the sequence """ - self._awgspecs['SR'] = SR + self._awgspecs["SR"] = SR def setChannelVoltageRange(self, channel, ampl, offset): """ @@ -267,17 +282,18 @@ def setChannelVoltageRange(self, channel, ampl, offset): ampl (float): The channel peak-to-peak amplitude (V) offset (float): The channel offset (V) """ - warnings.warn('Deprecation warning. This function is deprecated.' - ' Use setChannelAmplitude and SetChannelOffset ' - 'instead.') + warnings.warn( + "Deprecation warning. This function is deprecated." + " Use setChannelAmplitude and SetChannelOffset " + "instead." + ) keystr = f"channel{channel}_amplitude" self._awgspecs[keystr] = ampl keystr = f"channel{channel}_offset" self._awgspecs[keystr] = offset - def setChannelAmplitude(self, channel: Union[int, str], - ampl: float) -> None: + def setChannelAmplitude(self, channel: Union[int, str], ampl: float) -> None: """ Assign the physical voltage amplitude of the channel. This is used when making output for real instruments. @@ -289,8 +305,7 @@ def setChannelAmplitude(self, channel: Union[int, str], keystr = f"channel{channel}_amplitude" self._awgspecs[keystr] = ampl - def setChannelOffset(self, channel: Union[int, str], - offset: float) -> None: + def setChannelOffset(self, channel: Union[int, str], offset: float) -> None: """ Assign the physical voltage offset of the channel. This is used by some backends when making output for real instruments @@ -302,8 +317,7 @@ def setChannelOffset(self, channel: Union[int, str], keystr = f"channel{channel}_offset" self._awgspecs[keystr] = offset - def setChannelDelay(self, channel: Union[int, str], - delay: float) -> None: + def setChannelDelay(self, channel: Union[int, str], delay: float) -> None: """ Assign a delay to a channel. This is used when making output for .awg files. Use the delay to compensate for cable length differences etc. @@ -353,15 +367,16 @@ def setChannelFilterCompensation( SpecificationInconsistencyError: If both f_cut and tau are given. """ - if kind not in ['HP', 'LP']: - raise ValueError('Filter kind must either be "LP" (low pass) or ' - '"HP" (high pass).') + if kind not in ["HP", "LP"]: + raise ValueError( + 'Filter kind must either be "LP" (low pass) or "HP" (high pass).' + ) if not isinstance(order, int): - raise ValueError('Filter order must be an integer.') + raise ValueError("Filter order must be an integer.") if (f_cut is not None) and (tau is not None): - raise SpecificationInconsistencyError('Can not specify BOTH a time' - ' constant and a cut-off ' - 'frequency.') + raise SpecificationInconsistencyError( + "Can not specify BOTH a time constant and a cut-off frequency." + ) keystr = f"channel{channel}_filtercompensation" self._awgspecs[keystr] = { @@ -393,11 +408,15 @@ def addElement(self, position: int, element: Element) -> None: self._data.update({position: newelement}) # insert default sequencing settings - self._sequencing[position] = {'twait': 0, 'nrep': 1, - 'jump_input': 0, 'jump_target': 0, - 'goto': 0} + self._sequencing[position] = { + "twait": 0, + "nrep": 1, + "jump_input": 0, + "jump_target": 0, + "goto": 0, + } - def addSubSequence(self, position: int, subsequence: 'Sequence') -> None: + def addSubSequence(self, position: int, subsequence: "Sequence") -> None: """ Add a subsequence to the sequence. Overwrites anything previously assigned to this position. The subsequence can not contain any @@ -416,7 +435,7 @@ def addSubSequence(self, position: int, subsequence: 'Sequence') -> None: for elem in subsequence._data.values(): if isinstance(elem, Sequence): - raise ValueError('Subsequences can not contain subsequences.') + raise ValueError("Subsequences can not contain subsequences.") if subsequence.SR != self.SR: raise ValueError( @@ -426,9 +445,13 @@ def addSubSequence(self, position: int, subsequence: 'Sequence') -> None: self._data[position] = subsequence.copy() - self._sequencing[position] = {'twait': 0, 'nrep': 1, - 'jump_input': 0, 'jump_target': 0, - 'goto': 0} + self._sequencing[position] = { + "twait": 0, + "nrep": 1, + "jump_input": 0, + "jump_target": 0, + "goto": 0, + } def checkConsistency(self, verbose=False): """ @@ -438,9 +461,9 @@ def checkConsistency(self, verbose=False): # TODO: Give helpful info if the check fails try: - self._awgspecs['SR'] + self._awgspecs["SR"] except KeyError: - raise KeyError('No sample rate specified. Can not perform check') + raise KeyError("No sample rate specified. Can not perform check") # First check that all sample rates agree # Since all elements are validated on input, the SR exists @@ -448,7 +471,7 @@ def checkConsistency(self, verbose=False): if SRs == []: # case of empty Sequence SRs = [None] if SRs.count(SRs[0]) != len(SRs): - failmssg = ('checkConsistency failed: inconsistent sample rates.') + failmssg = "checkConsistency failed: inconsistent sample rates." log.info(failmssg) if verbose: print(failmssg) @@ -463,8 +486,10 @@ def checkConsistency(self, verbose=False): chans = None specchans = [None] if specchans.count(chans) != len(specchans): - failmssg = ('checkConsistency failed: different elements specify ' - 'different channels') + failmssg = ( + "checkConsistency failed: different elements specify " + "different channels" + ) log.info(failmssg) if verbose: print(failmssg) @@ -476,9 +501,11 @@ def checkConsistency(self, verbose=False): positions = list(self._data.keys()) if positions == []: # case of empty Sequence positions = [1] - if not positions == list(range(1, len(positions)+1)): - failmssg = ('checkConsistency failed: inconsistent sequence' - 'positions. Must be 1, 2, 3, ...') + if not positions == list(range(1, len(positions) + 1)): + failmssg = ( + "checkConsistency failed: inconsistent sequence" + "positions. Must be 1, 2, 3, ..." + ) log.info(failmssg) if verbose: print(failmssg) @@ -496,18 +523,20 @@ def description(self): for pos, elem in self._data.items(): desc[str(pos)] = {} - desc[str(pos)]['channels'] = elem.description + desc[str(pos)]["channels"] = elem.description try: sequencing = self._sequencing[pos] - seqdict = {'Wait trigger': sequencing['twait'], - 'Repeat': sequencing['nrep'], - 'jump_input': sequencing['jump_input'], - 'jump_target': sequencing['jump_target'], - 'Go to': sequencing['goto']} - desc[str(pos)]['sequencing'] = seqdict + seqdict = { + "Wait trigger": sequencing["twait"], + "Repeat": sequencing["nrep"], + "jump_input": sequencing["jump_input"], + "jump_target": sequencing["jump_target"], + "Go to": sequencing["goto"], + } + desc[str(pos)]["sequencing"] = seqdict except KeyError: - desc[str(pos)]['sequencing'] = 'Not set' - desc['awgspecs'] = self._awgspecs + desc[str(pos)]["sequencing"] = "Not set" + desc["awgspecs"] = self._awgspecs return desc def write_to_json(self, path_to_file: str) -> None: @@ -518,11 +547,11 @@ def write_to_json(self, path_to_file: str) -> None: path_to_file: the path to the file to write to ex: path_to_file/sequense.json """ - with open(path_to_file, 'w') as fp: + with open(path_to_file, "w") as fp: json.dump(self.description, fp, indent=4) @classmethod - def sequence_from_description(cls, seq_dict: dict) -> 'Sequence': + def sequence_from_description(cls, seq_dict: dict) -> "Sequence": """ Returns a sequence from a description given as a dict @@ -531,16 +560,18 @@ def sequence_from_description(cls, seq_dict: dict) -> 'Sequence': Sequence.description """ - awgspecs = seq_dict['awgspecs'] - SR = awgspecs['SR'] + awgspecs = seq_dict["awgspecs"] + SR = awgspecs["SR"] elem_list = list(seq_dict.keys()) new_instance = cls() for ele in elem_list[:-1]: - channels_list = list(seq_dict[ele]['channels'].keys()) + channels_list = list(seq_dict[ele]["channels"].keys()) elem = Element() for chan in channels_list: - bp_sum = BluePrint.blueprint_from_description(seq_dict[ele]['channels'][chan]) + bp_sum = BluePrint.blueprint_from_description( + seq_dict[ele]["channels"][chan] + ) bp_sum.setSR(SR) elem.addBluePrint(int(chan), bp_sum) if "flags" in seq_dict[ele]["channels"][chan]: @@ -554,18 +585,23 @@ def sequence_from_description(cls, seq_dict: dict) -> 'Sequence': new_instance.setChannelOffset(int(chan), ChannelOffset) new_instance.addElement(int(ele), elem) - sequencedict = seq_dict[ele]['sequencing'] - new_instance.setSequencingTriggerWait(int(ele), sequencedict['Wait trigger']) - new_instance.setSequencingNumberOfRepetitions(int(ele), sequencedict['Repeat']) - new_instance.setSequencingEventInput(int(ele), sequencedict['jump_input']) - new_instance.setSequencingEventJumpTarget(int(ele), sequencedict['jump_target']) - new_instance.setSequencingGoto(int(ele), sequencedict['Go to']) + sequencedict = seq_dict[ele]["sequencing"] + new_instance.setSequencingTriggerWait( + int(ele), sequencedict["Wait trigger"] + ) + new_instance.setSequencingNumberOfRepetitions( + int(ele), sequencedict["Repeat"] + ) + new_instance.setSequencingEventInput(int(ele), sequencedict["jump_input"]) + new_instance.setSequencingEventJumpTarget( + int(ele), sequencedict["jump_target"] + ) + new_instance.setSequencingGoto(int(ele), sequencedict["Go to"]) new_instance.setSR(SR) return new_instance - @classmethod - def init_from_json(cls, path_to_file: str) -> 'Sequence': + def init_from_json(cls, path_to_file: str) -> "Sequence": """ Reads sequense from JSON file @@ -590,7 +626,7 @@ def name(self): @name.setter def name(self, newname): if not isinstance(newname, str): - raise ValueError('The sequence name must be a string') + raise ValueError("The sequence name must be a string") self._name = newname @property @@ -606,7 +642,7 @@ def SR(self): Returns the sample rate, if defined. Else returns -1. """ try: - SR = self._awgspecs['SR'] + SR = self._awgspecs["SR"] except KeyError: SR = -1 @@ -620,8 +656,9 @@ def channels(self): if self.checkConsistency(): return self.element(1).channels else: - raise SequenceConsistencyError('Sequence not consistent. Can not' - ' figure out the channels.') + raise SequenceConsistencyError( + "Sequence not consistent. Can not figure out the channels." + ) @property def points(self): @@ -682,24 +719,25 @@ def _plotSummary(seq: dict[int, dict]) -> dict[int, dict[str, np.ndarray]]: output = {} # we assume correctness, all postions specify the same channels - chans = seq[1]['data'].keys() + chans = seq[1]["data"].keys() - minmax = dict(zip(chans, [(0, 0)]*len(chans))) + minmax = dict(zip(chans, [(0, 0)] * len(chans))) for element in seq.values(): - - arr_dict = element['data'] + arr_dict = element["data"] for chan in chans: - wfm = arr_dict[chan]['wfm'] + wfm = arr_dict[chan]["wfm"] if wfm.min() < minmax[chan][0]: minmax[chan] = (wfm.min(), minmax[chan][1]) if wfm.max() > minmax[chan][1]: minmax[chan] = (minmax[chan][0], wfm.max()) - output[chan] = {'wfm': np.array(minmax[chan]), - 'm1': np.zeros(2), - 'm2': np.zeros(2), - 'time': np.linspace(0, 1, 2)} + output[chan] = { + "wfm": np.array(minmax[chan]), + "m1": np.zeros(2), + "m2": np.zeros(2), + "time": np.linspace(0, 1, 2), + } return output @@ -727,9 +765,11 @@ def forge( """ # Validation if not self.checkConsistency(): - raise ValueError('Can not generate output. Something is ' - 'inconsistent. Please run ' - 'checkConsistency(verbose=True) for more details') + raise ValueError( + "Can not generate output. Something is " + "inconsistent. Please run " + "checkConsistency(verbose=True) for more details" + ) output: dict[int, dict] = {} channels = self.channels @@ -746,11 +786,11 @@ def forge( delays = [] for chan in channels: try: - delays.append(self._awgspecs[f'channel{chan}_delay']) + delays.append(self._awgspecs[f"channel{chan}_delay"]) except KeyError: delays.append(0) - for pos in range(1, seqlen+1): + for pos in range(1, seqlen + 1): if isinstance(data[pos], Sequence): subseq = data[pos] for elem in subseq._data.values(): @@ -759,55 +799,49 @@ def forge( data[pos]._applyDelays(delays) # forge arrays and form the output dict - for pos in range(1, seqlen+1): + for pos in range(1, seqlen + 1): output[pos] = {} - output[pos]['sequencing'] = self._sequencing[pos] + output[pos]["sequencing"] = self._sequencing[pos] if isinstance(data[pos], Sequence): subseq = data[pos] - output[pos]['type'] = 'subsequence' - output[pos]['content'] = {} - for pos2 in range(1, subseq.length_sequenceelements+1): - output[pos]['content'][pos2] = {'data': {}, - 'sequencing': {}} + output[pos]["type"] = "subsequence" + output[pos]["content"] = {} + for pos2 in range(1, subseq.length_sequenceelements + 1): + output[pos]["content"][pos2] = {"data": {}, "sequencing": {}} elem = subseq.element(pos2) dictdata = elem.getArrays(includetime=includetime) - output[pos]['content'][pos2]['data'] = dictdata + output[pos]["content"][pos2]["data"] = dictdata seqing = subseq._sequencing[pos2] - output[pos]['content'][pos2]['sequencing'] = seqing + output[pos]["content"][pos2]["sequencing"] = seqing # TODO: update sequencing elif isinstance(data[pos], Element): elem = data[pos] - output[pos]['type'] = 'element' + output[pos]["type"] = "element" dictdata = elem.getArrays(includetime=includetime) - output[pos]['content'] = {1: {'data': dictdata}} + output[pos]["content"] = {1: {"data": dictdata}} # apply filter corrections to forged arrays if apply_filters: - for pos1 in range(1, seqlen+1): - thiselem = output[pos1]['content'] + for pos1 in range(1, seqlen + 1): + thiselem = output[pos1]["content"] for pos2 in thiselem.keys(): - data = thiselem[pos2]['data'] + data = thiselem[pos2]["data"] for channame in data.keys(): - keystr = f'channel{channame}_filtercompensation' + keystr = f"channel{channame}_filtercompensation" if keystr in self._awgspecs.keys(): - kind = self._awgspecs[keystr]['kind'] - order = self._awgspecs[keystr]['order'] - f_cut = self._awgspecs[keystr]['f_cut'] - tau = self._awgspecs[keystr]['tau'] + kind = self._awgspecs[keystr]["kind"] + order = self._awgspecs[keystr]["order"] + f_cut = self._awgspecs[keystr]["f_cut"] + tau = self._awgspecs[keystr]["tau"] if f_cut is None: - f_cut = 1/tau - prefilter = data[channame]['wfm'] - postfilter = applyInverseRCFilter(prefilter, - self.SR, - kind, - f_cut, order, - DCgain=1) - (output[pos1] - ['content'] - [pos2] - ['data'] - [channame] - ['wfm']) = postfilter + f_cut = 1 / tau + prefilter = data[channame]["wfm"] + postfilter = applyInverseRCFilter( + prefilter, self.SR, kind, f_cut, order, DCgain=1 + ) + ( + output[pos1]["content"][pos2]["data"][channame]["wfm"] + ) = postfilter return output @@ -825,9 +859,11 @@ def _prepareForOutputting(self) -> list[dict[int, np.ndarray]]: """ # Validation if not self.checkConsistency(): - raise ValueError('Can not generate output. Something is ' - 'inconsistent. Please run ' - 'checkConsistency(verbose=True) for more details') + raise ValueError( + "Can not generate output. Something is " + "inconsistent. Please run " + "checkConsistency(verbose=True) for more details" + ) # # channels = self.element(1).channels # all elements have ident. chans @@ -836,9 +872,10 @@ def _prepareForOutputting(self) -> list[dict[int, np.ndarray]]: data = deepcopy(self._data) seqlen = len(data.keys()) # check if sequencing information is specified for each element - if not sorted(list(self._sequencing.keys())) == list(range(1, seqlen+1)): - raise ValueError('Can not generate output for file; ' - 'incorrect sequencer information.') + if not sorted(list(self._sequencing.keys())) == list(range(1, seqlen + 1)): + raise ValueError( + "Can not generate output for file; incorrect sequencer information." + ) # Verify physical amplitude specifiations for chan in channels: @@ -857,13 +894,13 @@ def _prepareForOutputting(self) -> list[dict[int, np.ndarray]]: delays.append(0) maxdelay = max(delays) - for pos in range(1, seqlen+1): + for pos in range(1, seqlen + 1): for chanind, chan in enumerate(channels): element = data[pos] delay = delays[chanind] - if 'blueprint' in element._data[chan].keys(): - blueprint = element._data[chan]['blueprint'] + if "blueprint" in element._data[chan].keys(): + blueprint = element._data[chan]["blueprint"] # prevent information about flags to be lost if "flags" in element._data[chan].keys(): flags = element._data[chan]["flags"] @@ -872,17 +909,17 @@ def _prepareForOutputting(self) -> list[dict[int, np.ndarray]]: # update existing waituntils for segpos in range(len(blueprint._funlist)): - if blueprint._funlist[segpos] == 'waituntil': + if blueprint._funlist[segpos] == "waituntil": oldwait = blueprint._argslist[segpos][0] - blueprint._argslist[segpos] = (oldwait+delay,) + blueprint._argslist[segpos] = (oldwait + delay,) # insert delay before the waveform if delay > 0: - blueprint.insertSegment(0, 'waituntil', (delay,), - 'waituntil') + blueprint.insertSegment(0, "waituntil", (delay,), "waituntil") # add zeros at the end - if maxdelay-delay > 0: - blueprint.insertSegment(-1, PulseAtoms.ramp, (0, 0), - dur=maxdelay-delay) + if maxdelay - delay > 0: + blueprint.insertSegment( + -1, PulseAtoms.ramp, (0, 0), dur=maxdelay - delay + ) # TODO: is the next line even needed? # If not, remove the code updating the flags below # and the one remembering them above @@ -893,33 +930,31 @@ def _prepareForOutputting(self) -> list[dict[int, np.ndarray]]: else: arrays = element._data[chan]["array"] for name, arr in arrays.items(): - pre_wait = np.zeros(int(delay/self.SR)) - post_wait = np.zeros(int((maxdelay-delay)/self.SR)) - arrays[name] = np.concatenate((pre_wait, arr, - post_wait)) + pre_wait = np.zeros(int(delay / self.SR)) + post_wait = np.zeros(int((maxdelay - delay) / self.SR)) + arrays[name] = np.concatenate((pre_wait, arr, post_wait)) # Now forge all the elements as specified elements = [] # the forged elements - for pos in range(1, seqlen+1): + for pos in range(1, seqlen + 1): elements.append(data[pos].getArrays()) # Now that the numerical arrays exist, we can apply filter compensation for chan in channels: keystr = f"channel{chan}_filtercompensation" if keystr in self._awgspecs.keys(): - kind = self._awgspecs[keystr]['kind'] - order = self._awgspecs[keystr]['order'] - f_cut = self._awgspecs[keystr]['f_cut'] - tau = self._awgspecs[keystr]['tau'] + kind = self._awgspecs[keystr]["kind"] + order = self._awgspecs[keystr]["order"] + f_cut = self._awgspecs[keystr]["f_cut"] + tau = self._awgspecs[keystr]["tau"] if f_cut is None: - f_cut = 1/tau + f_cut = 1 / tau for pos in range(seqlen): - prefilter = elements[pos][chan]['wfm'] - postfilter = applyInverseRCFilter(prefilter, - self.SR, - kind, f_cut, order, - DCgain=1) - elements[pos][chan]['wfm'] = postfilter + prefilter = elements[pos][chan]["wfm"] + postfilter = applyInverseRCFilter( + prefilter, self.SR, kind, f_cut, order, DCgain=1 + ) + elements[pos][chan]["wfm"] = postfilter return elements @@ -982,8 +1017,8 @@ def outputForSEQXFile( if len(amplitudes) == 1: amplitudes.append(0) - for pos in range(1, seqlen+1): - element = elements[pos-1] + for pos in range(1, seqlen + 1): + element = elements[pos - 1] for chan in channels: ampl = self._awgspecs[f"channel{chan}_amplitude"] wfm = element[chan]["wfm"] @@ -1024,18 +1059,18 @@ def outputForSEQXFile( # Since sequencing options are valid/invalid differently for # different backends, we make the validation here - for pos in range(1, seqlen+1): + for pos in range(1, seqlen + 1): for chanind, chan in enumerate(channels): - wfm = elements[pos-1][chan]['wfm'] - m1 = elements[pos-1][chan]['m1'] - m2 = elements[pos-1][chan]['m2'] + wfm = elements[pos - 1][chan]["wfm"] + m1 = elements[pos - 1][chan]["m1"] + m2 = elements[pos - 1][chan]["m2"] waveforms[chanind].append(np.array([wfm, m1, m2])) - twait = self._sequencing[pos]['twait'] - nrep = self._sequencing[pos]['nrep'] - jump_to = self._sequencing[pos]['jump_target'] - jump_state = self._sequencing[pos]['jump_input'] - goto = self._sequencing[pos]['goto'] + twait = self._sequencing[pos]["twait"] + nrep = self._sequencing[pos]["nrep"] + jump_to = self._sequencing[pos]["jump_target"] + jump_state = self._sequencing[pos]["jump_input"] + goto = self._sequencing[pos]["goto"] if twait not in [0, 1, 2, 3]: raise SequencingError( @@ -1080,8 +1115,16 @@ def outputForSEQXFile( jump_states.append(jump_state) gotos.append(goto) - return (trig_waits, nreps, jump_states, jump_tos, gotos, - waveforms, amplitudes, self.name) + return ( + trig_waits, + nreps, + jump_states, + jump_tos, + gotos, + waveforms, + amplitudes, + self.name, + ) def outputForSEQXFileWithFlags( self, @@ -1157,9 +1200,10 @@ def outputForAWGFile(self): # -ampl/2+off. # def rescaler(val, ampl, off): - return val/ampl*2-off - for pos in range(1, seqlen+1): - element = elements[pos-1] + return val / ampl * 2 - off + + for pos in range(1, seqlen + 1): + element = elements[pos - 1] for chan in channels: ampl = self._awgspecs[f"channel{chan}_amplitude"] off = self._awgspecs[f"channel{chan}_offset"] @@ -1180,8 +1224,8 @@ def rescaler(val, ampl, off): f"{wfm.min()} < {-ampl/2+off}!" ) wfm = rescaler(wfm, ampl, off) - element[chan]['wfm'] = wfm - elements[pos-1] = element + element[chan]["wfm"] = wfm + elements[pos - 1] = element # Finally cast the lists into the shapes required by the AWG driver waveforms = [[] for dummy in range(len(channels))] @@ -1194,16 +1238,16 @@ def rescaler(val, ampl, off): # Since sequencing options are valid/invalid differently for # different backends, we make the validation here - for pos in range(1, seqlen+1): + for pos in range(1, seqlen + 1): for chanind, chan in enumerate(channels): - waveforms[chanind].append(elements[pos-1][chan]['wfm']) - m1s[chanind].append(elements[pos-1][chan]['m1']) - m2s[chanind].append(elements[pos-1][chan]['m2']) + waveforms[chanind].append(elements[pos - 1][chan]["wfm"]) + m1s[chanind].append(elements[pos - 1][chan]["m1"]) + m2s[chanind].append(elements[pos - 1][chan]["m2"]) - twait = self._sequencing[pos]['twait'] - nrep = self._sequencing[pos]['nrep'] - jump_to = self._sequencing[pos]['jump_target'] - goto = self._sequencing[pos]['goto'] + twait = self._sequencing[pos]["twait"] + nrep = self._sequencing[pos]["nrep"] + jump_to = self._sequencing[pos]["jump_target"] + goto = self._sequencing[pos]["goto"] if twait not in [0, 1]: raise SequencingError( @@ -1241,8 +1285,8 @@ def rescaler(val, ampl, off): gotos.append(goto) # ...and make a sliceable object out of them - output = _AWGOutput((waveforms, m1s, m2s, nreps, - trig_waits, gotos, - jump_tos), self.channels) + output = _AWGOutput( + (waveforms, m1s, m2s, nreps, trig_waits, gotos, jump_tos), self.channels + ) return output diff --git a/src/broadbean/tools.py b/src/broadbean/tools.py index 2ab85973d..98e9a4de6 100644 --- a/src/broadbean/tools.py +++ b/src/broadbean/tools.py @@ -10,8 +10,7 @@ log = logging.getLogger(__name__) -def makeLinearlyVaryingSequence(baseelement, channel, name, arg, start, stop, - step): +def makeLinearlyVaryingSequence(baseelement, channel, name, arg, start, stop, step): """ Make a pulse sequence where a single parameter varies linearly. The pulse sequence will consist of N copies of the same element with just @@ -36,15 +35,15 @@ def makeLinearlyVaryingSequence(baseelement, channel, name, arg, start, stop, sequence.setSR(baseelement.SR) - iterator = np.linspace(start, stop, round(abs(stop-start)/step)+1) + iterator = np.linspace(start, stop, round(abs(stop - start) / step) + 1) for ind, val in enumerate(iterator): element = baseelement.copy() - if arg == 'duration': + if arg == "duration": element.changeDuration(channel, name, val) else: element.changeArg(channel, name, arg, val) - sequence.addElement(ind+1, element) + sequence.addElement(ind + 1, element) return sequence @@ -79,36 +78,38 @@ def makeVaryingSequence(baseelement, channels, names, args, iters): inputlengths = [len(channels), len(names), len(args), len(iters)] if not inputlengths.count(inputlengths[0]) == len(inputlengths): - raise ValueError('Inconsistent number of channel, names, args, and ' - 'parameter sequences. Please specify the same number ' - 'of each.') + raise ValueError( + "Inconsistent number of channel, names, args, and " + "parameter sequences. Please specify the same number " + "of each." + ) noofvals = [len(itr) for itr in iters] if not noofvals.count(noofvals[0]) == len(iters): - raise ValueError('Not the same number of values in each parameter ' - 'value sequence (input argument: iters)') + raise ValueError( + "Not the same number of values in each parameter " + "value sequence (input argument: iters)" + ) sequence = Sequence() sequence.setSR(baseelement.SR) - for elnum in range(1, noofvals[0]+1): + for elnum in range(1, noofvals[0] + 1): sequence.addElement(elnum, baseelement.copy()) - for (chan, name, arg, vals) in zip(channels, names, args, iters): + for chan, name, arg, vals in zip(channels, names, args, iters): for mpos, val in enumerate(vals): - element = sequence.element(mpos+1) - if arg == 'duration': + element = sequence.element(mpos + 1) + if arg == "duration": element.changeDuration(chan, name, val) else: element.changeArg(chan, name, arg, val) - log.info('Created varying sequence using makeVaryingSequence.' - ' Now validating it...') + log.info("Created varying sequence using makeVaryingSequence. Now validating it...") if not sequence.checkConsistency(): - raise SequenceConsistencyError('Invalid sequence. See log for ' - 'details.') + raise SequenceConsistencyError("Invalid sequence. See log for details.") else: - log.info('Valid sequence') + log.info("Valid sequence") return sequence @@ -133,20 +134,26 @@ def repeatAndVarySequence(seq, poss, channels, names, args, iters): """ if not seq.checkConsistency(): - raise SequenceConsistencyError('Inconsistent input sequence! Can not ' - 'proceed. Check all positions ' - 'and channels.') + raise SequenceConsistencyError( + "Inconsistent input sequence! Can not " + "proceed. Check all positions " + "and channels." + ) inputlens = [len(poss), len(channels), len(names), len(args), len(iters)] if not inputlens.count(inputlens[0]) == len(inputlens): - raise ValueError('Inconsistent number of position, channel, name, args' - ', and ' - 'parameter sequences. Please specify the same number ' - 'of each.') + raise ValueError( + "Inconsistent number of position, channel, name, args" + ", and " + "parameter sequences. Please specify the same number " + "of each." + ) noofvals = [len(itr) for itr in iters] if not noofvals.count(noofvals[0]) == len(iters): - raise ValueError('Not the same number of values in each parameter ' - 'value sequence (input argument: iters)') + raise ValueError( + "Not the same number of values in each parameter " + "value sequence (input argument: iters)" + ) newseq = Sequence() newseq._awgspecs = seq._awgspecs @@ -155,12 +162,11 @@ def repeatAndVarySequence(seq, poss, channels, names, args, iters): for step in range(no_of_steps): tempseq = seq.copy() - for (pos, chan, name, arg, vals) in zip(poss, channels, names, - args, iters): + for pos, chan, name, arg, vals in zip(poss, channels, names, args, iters): element = tempseq.element(pos) val = vals[step] - if arg == 'duration': + if arg == "duration": element.changeDuration(chan, name, val) else: element.changeArg(chan, name, arg, val) diff --git a/tests/test_awgfilegeneration.py b/tests/test_awgfilegeneration.py index b3a2b455a..b72094cf8 100644 --- a/tests/test_awgfilegeneration.py +++ b/tests/test_awgfilegeneration.py @@ -15,13 +15,12 @@ @pytest.fixture def protosequence1(): - SR = 1e9 th = bb.BluePrint() - th.insertSegment(0, ramp, args=(0, 0), name='ramp', dur=10e-6) - th.insertSegment(1, ramp, args=(1, 1), name='ramp', dur=5e-6) - th.insertSegment(2, ramp, args=(0, 0), name='ramp', dur=10e-6) + th.insertSegment(0, ramp, args=(0, 0), name="ramp", dur=10e-6) + th.insertSegment(1, ramp, args=(1, 1), name="ramp", dur=5e-6) + th.insertSegment(2, ramp, args=(0, 0), name="ramp", dur=10e-6) th.setSR(SR) wiggle1 = bb.BluePrint() @@ -44,7 +43,7 @@ def protosequence1(): seq.addElement(1, elem1) seq.addElement(2, elem2) seq.setSR(SR) - seq.name = 'protoSequence' + seq.name = "protoSequence" seq.setChannelAmplitude(1, 2) seq.setChannelAmplitude(2, 2) @@ -61,7 +60,6 @@ def protosequence1(): def test_awg_output(protosequence1): - # basic check: no exceptions should be raised package = protosequence1.outputForAWGFile() @@ -79,18 +77,21 @@ def should_raise_sequencingerror(wait, nrep, jump_to, goto, num_elms): return True if nrep not in range(0, 16384): return True - if jump_to not in range(-1, num_elms+1): + if jump_to not in range(-1, num_elms + 1): return True - if goto not in range(0, num_elms+1): + if goto not in range(0, num_elms + 1): return True return False @settings(max_examples=25, suppress_health_check=(HealthCheck.function_scoped_fixture,)) -@given(wait=hst.integers(), nrep=hst.integers(), jump_to=hst.integers(), - goto=hst.integers()) +@given( + wait=hst.integers(), + nrep=hst.integers(), + jump_to=hst.integers(), + goto=hst.integers(), +) def test_awg_output_validations(protosequence1, wait, nrep, jump_to, goto): - protosequence1.setSequencingTriggerWait(1, wait) protosequence1.setSequencingNumberOfRepetitions(1, nrep) protosequence1.setSequencingEventJumpTarget(1, jump_to) diff --git a/tests/test_blueprint.py b/tests/test_blueprint.py index 6ae995ee4..19f99e71d 100644 --- a/tests/test_blueprint.py +++ b/tests/test_blueprint.py @@ -30,9 +30,9 @@ def blueprint_tophat(): similar to a tophat """ th = bb.BluePrint() - th.insertSegment(0, ramp, args=(0, 0), name='ramp', dur=1) - th.insertSegment(1, ramp, args=(1, 1), name='ramp', dur=0.5) - th.insertSegment(2, ramp, args=(0, 0), name='ramp', dur=1) + th.insertSegment(0, ramp, args=(0, 0), name="ramp", dur=1) + th.insertSegment(1, ramp, args=(1, 1), name="ramp", dur=0.5) + th.insertSegment(2, ramp, args=(0, 0), name="ramp", dur=1) th.setSR(tophat_SR) return th @@ -44,47 +44,52 @@ def blueprint_nasty(): Return a nasty blueprint trying to hit some corner cases """ ns = bb.BluePrint() - ns.insertSegment(0, 'waituntil', args=(1,)) - ns.insertSegment(1, ramp, (-1/3, 1/3), dur=0.1) - ns.insertSegment(2, 'waituntil', args=(1+2/3,)) + ns.insertSegment(0, "waituntil", args=(1,)) + ns.insertSegment(1, ramp, (-1 / 3, 1 / 3), dur=0.1) + ns.insertSegment(2, "waituntil", args=(1 + 2 / 3,)) ns.setSR(tophat_SR) - ns.setSegmentMarker('ramp', (-0.1, 0.1), 1) - ns.setSegmentMarker('waituntil2', (0, 2/3), 2) + ns.setSegmentMarker("ramp", (-0.1, 0.1), 1) + ns.setSegmentMarker("waituntil2", (0, 2 / 3), 2) ns.marker1 = [(0, 0.1)] ns.marker2 = [(1, 0.1)] return ns + ################################################## # TEST STATIC METHODS -@pytest.mark.parametrize('inp, outp', [('', ''), - ('test', 'test'), - ('test3', 'test'), - ('2test3', '2test'), - ('123_', '123_'), - ('123_4', '123_')]) +@pytest.mark.parametrize( + "inp, outp", + [ + ("", ""), + ("test", "test"), + ("test3", "test"), + ("2test3", "2test"), + ("123_", "123_"), + ("123_4", "123_"), + ], +) def test_basename(inp, outp): assert bb.BluePrint._basename(inp) == outp -@pytest.mark.parametrize('notstring', [(1, 1.1, [], (), - ('name1',), - ['name2'])]) +@pytest.mark.parametrize("notstring", [(1, 1.1, [], (), ("name1",), ["name2"])]) def test_basename_input(notstring): with pytest.raises(ValueError): bb.BluePrint._basename(notstring) -namelistsinout = [(['name', 'name'], ['name', 'name2']), - (['ramp', 'sine', 'ramp', 'sine'], - ['ramp', 'sine', 'ramp2', 'sine2']), - (['name2', 'name'], ['name', 'name2']), - (['n3', 'n2', 'n1'], ['n', 'n2', 'n3']), - (['n', '2n', 'ss1', 'ss3'], ['n', '2n', 'ss', 'ss2'])] +namelistsinout = [ + (["name", "name"], ["name", "name2"]), + (["ramp", "sine", "ramp", "sine"], ["ramp", "sine", "ramp2", "sine2"]), + (["name2", "name"], ["name", "name2"]), + (["n3", "n2", "n1"], ["n", "n2", "n3"]), + (["n", "2n", "ss1", "ss3"], ["n", "2n", "ss", "ss2"]), +] def test_make_names_unique0(): @@ -119,7 +124,8 @@ def test_make_names_unique4(): def test_make_names_unique_input(): with pytest.raises(ValueError): - bb.BluePrint._make_names_unique('name') + bb.BluePrint._make_names_unique("name") + ################################################## # TEST BARE INITIALISATION @@ -129,109 +135,135 @@ def test_creation(virgin_blueprint): assert isinstance(virgin_blueprint, bb.BluePrint) -@pytest.mark.parametrize("attribute, expected", [('_funlist', []), - ('_namelist', []), - ('_argslist', []), - ('_durslist', []), - ('marker1', []), - ('marker2', []), - ('_segmark1', []), - ('_segmark2', [])]) +@pytest.mark.parametrize( + "attribute, expected", + [ + ("_funlist", []), + ("_namelist", []), + ("_argslist", []), + ("_durslist", []), + ("marker1", []), + ("marker2", []), + ("_segmark1", []), + ("_segmark2", []), + ], +) def test_bare_init(virgin_blueprint, attribute, expected): assert virgin_blueprint.__getattribute__(attribute) == expected + ################################################## # TEST WITH TOPHAT -@pytest.mark.parametrize("attribute, val", [('_funlist', [ramp, ramp, ramp]), - ('_namelist', - ['ramp', 'ramp2', 'ramp3']), - ('_argslist', - [(0, 0), (1, 1), (0, 0)]), - ('_durslist', - [1, 0.5, 1]), - ('marker1', []), - ('marker2', []), - ('_segmark1', [(0, 0)]*3), - ('_segmark2', [(0, 0)]*3)]) +@pytest.mark.parametrize( + "attribute, val", + [ + ("_funlist", [ramp, ramp, ramp]), + ("_namelist", ["ramp", "ramp2", "ramp3"]), + ("_argslist", [(0, 0), (1, 1), (0, 0)]), + ("_durslist", [1, 0.5, 1]), + ("marker1", []), + ("marker2", []), + ("_segmark1", [(0, 0)] * 3), + ("_segmark2", [(0, 0)] * 3), + ], +) def test_tophat_init(blueprint_tophat, attribute, val): assert blueprint_tophat.__getattribute__(attribute) == val -@pytest.mark.parametrize("attribute, val", [('_funlist', [ramp, ramp, ramp]), - ('_namelist', - ['ramp', 'ramp2', 'ramp3']), - ('_argslist', - [(0, 0), (1, 1), (0, 0)]), - ('_durslist', - [1, 0.5, 1]), - ('marker1', []), - ('marker2', []), - ('_segmark1', [(0, 0)]*3), - ('_segmark2', [(0, 0)]*3)]) +@pytest.mark.parametrize( + "attribute, val", + [ + ("_funlist", [ramp, ramp, ramp]), + ("_namelist", ["ramp", "ramp2", "ramp3"]), + ("_argslist", [(0, 0), (1, 1), (0, 0)]), + ("_durslist", [1, 0.5, 1]), + ("marker1", []), + ("marker2", []), + ("_segmark1", [(0, 0)] * 3), + ("_segmark2", [(0, 0)] * 3), + ], +) def test_tophat_copy(blueprint_tophat, attribute, val): new_bp = blueprint_tophat.copy() assert new_bp.__getattribute__(attribute) == val -@pytest.mark.parametrize('name, newdur, durslist', - [('ramp', 0.1, [0.1, 0.5, 1]), - ('ramp2', 0.1, [1, 0.1, 1]), - ('ramp3', 0.1, [1, 0.5, 0.1])]) +@pytest.mark.parametrize( + "name, newdur, durslist", + [ + ("ramp", 0.1, [0.1, 0.5, 1]), + ("ramp2", 0.1, [1, 0.1, 1]), + ("ramp3", 0.1, [1, 0.5, 0.1]), + ], +) def test_tophat_changeduration(blueprint_tophat, name, newdur, durslist): blueprint_tophat.changeDuration(name, newdur) assert blueprint_tophat._durslist == durslist def test_tophat_changeduration_everywhere(blueprint_tophat): - blueprint_tophat.changeDuration('ramp', 0.2, replaceeverywhere=True) - assert blueprint_tophat._durslist == [0.2]*3 + blueprint_tophat.changeDuration("ramp", 0.2, replaceeverywhere=True) + assert blueprint_tophat._durslist == [0.2] * 3 -@pytest.mark.parametrize('newdur', [-1, 0.0, 1/(tophat_SR+1), None]) +@pytest.mark.parametrize("newdur", [-1, 0.0, 1 / (tophat_SR + 1), None]) def test_tophat_changeduration_valueerror(blueprint_tophat, newdur): with pytest.raises(ValueError): - blueprint_tophat.changeDuration('ramp', newdur) - - -@pytest.mark.parametrize('name, arg, newval, argslist', - [('ramp', 'start', -1, [(-1, 0), (1, 1), (0, 0)]), - ('ramp', 'stop', -1, [(0, -1), (1, 1), (0, 0)]), - ('ramp', 0, -1, [(-1, 0), (1, 1), (0, 0)]), - ('ramp', 1, -1, [(0, -1), (1, 1), (0, 0)]), - ('ramp2', 'stop', -1, [(0, 0), (1, -1), (0, 0)])]) + blueprint_tophat.changeDuration("ramp", newdur) + + +@pytest.mark.parametrize( + "name, arg, newval, argslist", + [ + ("ramp", "start", -1, [(-1, 0), (1, 1), (0, 0)]), + ("ramp", "stop", -1, [(0, -1), (1, 1), (0, 0)]), + ("ramp", 0, -1, [(-1, 0), (1, 1), (0, 0)]), + ("ramp", 1, -1, [(0, -1), (1, 1), (0, 0)]), + ("ramp2", "stop", -1, [(0, 0), (1, -1), (0, 0)]), + ], +) def test_tophat_changeargument(blueprint_tophat, name, arg, newval, argslist): blueprint_tophat.changeArg(name, arg, newval) assert blueprint_tophat._argslist == argslist -@pytest.mark.parametrize('name, arg, newval, argslist', - [('ramp', 'start', -1, [(-1, 0), (-1, 1), (-1, 0)]), - ('ramp', 'stop', -1, [(0, -1), (1, -1), (0, -1)]), - ('ramp', 0, -1, [(-1, 0), (-1, 1), (-1, 0)]), - ('ramp', 1, -1, [(0, -1), (1, -1), (0, -1)])]) -def test_tophat_changeargument_replaceeverywhere(blueprint_tophat, name, - arg, newval, argslist): +@pytest.mark.parametrize( + "name, arg, newval, argslist", + [ + ("ramp", "start", -1, [(-1, 0), (-1, 1), (-1, 0)]), + ("ramp", "stop", -1, [(0, -1), (1, -1), (0, -1)]), + ("ramp", 0, -1, [(-1, 0), (-1, 1), (-1, 0)]), + ("ramp", 1, -1, [(0, -1), (1, -1), (0, -1)]), + ], +) +def test_tophat_changeargument_replaceeverywhere( + blueprint_tophat, name, arg, newval, argslist +): blueprint_tophat.changeArg(name, arg, newval, replaceeverywhere=True) assert blueprint_tophat._argslist == argslist -@pytest.mark.parametrize('name, arg', [('ramp', 'freq'), - ('ramp', -1), - ('ramp', 2), - ('ramp2', ''), - ('ramp4', 1)]) +@pytest.mark.parametrize( + "name, arg", + [("ramp", "freq"), ("ramp", -1), ("ramp", 2), ("ramp2", ""), ("ramp4", 1)], +) def test_tophat_changeargument_valueerror(blueprint_tophat, name, arg): with pytest.raises(ValueError): blueprint_tophat.changeArg(name, arg, 0) -@pytest.mark.parametrize('pos, func, funlist', - [(0, ramp, [ramp, ramp, ramp, ramp]), - (-1, sine, [ramp, ramp, ramp, sine]), - (2, sine, [ramp, ramp, sine, ramp]), - (3, sine, [ramp, ramp, ramp, sine])]) +@pytest.mark.parametrize( + "pos, func, funlist", + [ + (0, ramp, [ramp, ramp, ramp, ramp]), + (-1, sine, [ramp, ramp, ramp, sine]), + (2, sine, [ramp, ramp, sine, ramp]), + (3, sine, [ramp, ramp, ramp, sine]), + ], +) def test_tophat_insert_funlist(blueprint_tophat, pos, func, funlist): blueprint_tophat.insertSegment(pos, func, args=(1, 0), dur=1) assert blueprint_tophat._funlist == funlist @@ -240,49 +272,56 @@ def test_tophat_insert_funlist(blueprint_tophat, pos, func, funlist): newargs = (5, 5) -@pytest.mark.parametrize('pos, argslist', - [(0, [newargs, (0, 0), (1, 1), (0, 0)]), - (-1, [(0, 0), (1, 1), (0, 0), newargs]), - (2, [(0, 0), (1, 1), newargs, (0, 0)])]) +@pytest.mark.parametrize( + "pos, argslist", + [ + (0, [newargs, (0, 0), (1, 1), (0, 0)]), + (-1, [(0, 0), (1, 1), (0, 0), newargs]), + (2, [(0, 0), (1, 1), newargs, (0, 0)]), + ], +) def test_tophat_insert_argslist(blueprint_tophat, pos, argslist): blueprint_tophat.insertSegment(pos, ramp, newargs, dur=1) assert blueprint_tophat._argslist == argslist -@pytest.mark.parametrize('pos, name, namelist', - [(0, 'myramp', ['myramp', 'ramp', 'ramp2', 'ramp3']), - (-1, 'myramp', ['ramp', 'ramp2', 'ramp3', 'myramp']), - (2, 'ramp', ['ramp', 'ramp2', 'ramp3', 'ramp4'])]) +@pytest.mark.parametrize( + "pos, name, namelist", + [ + (0, "myramp", ["myramp", "ramp", "ramp2", "ramp3"]), + (-1, "myramp", ["ramp", "ramp2", "ramp3", "myramp"]), + (2, "ramp", ["ramp", "ramp2", "ramp3", "ramp4"]), + ], +) def test_tophat_insert_namelist(blueprint_tophat, pos, name, namelist): blueprint_tophat.insertSegment(pos, ramp, newargs, name=name, dur=0.5) assert blueprint_tophat._namelist == namelist -@pytest.mark.parametrize('name', ['ramp', 'ramp2', 'ramp3', 'ramp4']) +@pytest.mark.parametrize("name", ["ramp", "ramp2", "ramp3", "ramp4"]) def test_tophat_remove_namelist(blueprint_tophat, name): if name in blueprint_tophat._namelist: blueprint_tophat.removeSegment(name) - assert blueprint_tophat._namelist == ['ramp', 'ramp2'] + assert blueprint_tophat._namelist == ["ramp", "ramp2"] else: with pytest.raises(KeyError): blueprint_tophat.removeSegment(name) def test_tophat_remove_segmentmarker(blueprint_tophat): - with pytest.raises(ValueError): - blueprint_tophat.removeSegmentMarker('ramp', 3) + blueprint_tophat.removeSegmentMarker("ramp", 3) with pytest.raises(ValueError): - blueprint_tophat.removeSegmentMarker('no such name', 3) + blueprint_tophat.removeSegmentMarker("no such name", 3) # Adding and removing should be equivalent to NOOP bpc = blueprint_tophat.copy() - blueprint_tophat.setSegmentMarker('ramp', (0, 0.1), 1) - blueprint_tophat.removeSegmentMarker('ramp', 1) + blueprint_tophat.setSegmentMarker("ramp", (0, 0.1), 1) + blueprint_tophat.removeSegmentMarker("ramp", 1) assert bpc == blueprint_tophat with pytest.raises(KeyError): - bpc.removeSegmentMarker('no such name', 1) + bpc.removeSegmentMarker("no such name", 1) ################################################## @@ -293,9 +332,9 @@ def test_not_equal(blueprint_tophat): bpc = blueprint_tophat.copy() with pytest.raises(ValueError): - bpc == '1' + bpc == "1" - bpc.insertSegment(0, ramp, (0, 0), dur=1/3) + bpc.insertSegment(0, ramp, (0, 0), dur=1 / 3) assert (bpc == blueprint_tophat) is False @@ -306,7 +345,7 @@ def test_not_equal(blueprint_tophat): assert (bpc == blueprint_tophat) is False bpc = blueprint_tophat.copy() - bpc.setSegmentMarker('ramp', (0, 0.1), 1) + bpc.setSegmentMarker("ramp", (0, 0.1), 1) assert (bpc == blueprint_tophat) is False @@ -316,7 +355,7 @@ def test_not_equal(blueprint_tophat): assert (bpc == blueprint_tophat) is False bpc = blueprint_tophat.copy() - blueprint_tophat.setSegmentMarker('ramp', (0, 0.1), 2) + blueprint_tophat.setSegmentMarker("ramp", (0, 0.1), 2) assert (bpc == blueprint_tophat) is False @@ -328,8 +367,7 @@ def test_add_two_identical(blueprint_tophat): new_bp2 = bp + bpc assert new_bp1 == new_bp2 - assert new_bp1._namelist == ['ramp', 'ramp2', 'ramp3', 'ramp4', 'ramp5', - 'ramp6'] + assert new_bp1._namelist == ["ramp", "ramp2", "ramp3", "ramp4", "ramp5", "ramp6"] assert new_bp1._argslist == bp._argslist + bp._argslist assert new_bp1._funlist == bp._funlist + bp._funlist assert new_bp1._segmark1 == bp._segmark1 + bp._segmark1 @@ -345,8 +383,14 @@ def test_add_two_different(blueprint_tophat, blueprint_nasty): bp = th + ns - assert bp._namelist == ['ramp', 'ramp2', 'ramp3', - 'waituntil', 'ramp4', 'waituntil2'] + assert bp._namelist == [ + "ramp", + "ramp2", + "ramp3", + "waituntil", + "ramp4", + "waituntil2", + ] assert bp._argslist == th._argslist + ns._argslist assert bp._funlist == th._funlist + ns._funlist assert bp._segmark1 == th._segmark1 + ns._segmark1 @@ -364,10 +408,18 @@ def test_description(blueprint_nasty, blueprint_tophat): desc1 = blueprint_nasty.description desc2 = blueprint_tophat.description - exp_keys = ['marker1_abs', 'marker1_rel', 'marker2_abs', 'marker2_rel', - 'segment_01', 'segment_02', 'segment_03'] + exp_keys = [ + "marker1_abs", + "marker1_rel", + "marker2_abs", + "marker2_rel", + "segment_01", + "segment_02", + "segment_03", + ] assert sorted(list(desc1.keys())) == sorted(exp_keys) assert sorted(list(desc2.keys())) == sorted(exp_keys) + # More to come... diff --git a/tests/test_element.py b/tests/test_element.py index b03735ee9..3ba9cfc9c 100644 --- a/tests/test_element.py +++ b/tests/test_element.py @@ -18,16 +18,16 @@ tophat_SR = 2000 -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def blueprint_tophat(): """ Return a blueprint consisting of three slopeless ramps forming something similar to a tophat """ th = bb.BluePrint() - th.insertSegment(0, ramp, args=(0, 0), name='ramp', dur=1) - th.insertSegment(1, ramp, args=(1, 1), name='ramp', dur=0.5) - th.insertSegment(2, ramp, args=(0, 0), name='ramp', dur=1) + th.insertSegment(0, ramp, args=(0, 0), name="ramp", dur=1) + th.insertSegment(1, ramp, args=(1, 1), name="ramp", dur=0.5) + th.insertSegment(2, ramp, args=(0, 0), name="ramp", dur=1) th.setSR(tophat_SR) return th @@ -75,7 +75,7 @@ def test_equality_false(blueprint_tophat): elem2 = Element() elem1.addBluePrint(1, blueprint_tophat) elem2.addBluePrint(1, blueprint_tophat) - elem1.changeArg(1, 'ramp', 'start', 2) + elem1.changeArg(1, "ramp", "start", 2) assert elem1 != elem2 @@ -85,17 +85,17 @@ def test_copy(blueprint_tophat): elem2 = elem1.copy() assert elem1 == elem2 + ################################################## # Adding things to the Element goes hand in hand # with duration validation def test_addArray(): - SR = 1e9 N = 2500 - wfm = np.linspace(0, N/SR, N) + wfm = np.linspace(0, N / SR, N) m1 = np.zeros(N) m2 = np.ones(N) @@ -106,13 +106,13 @@ def test_addArray(): assert np.all(output[1]["wfm"] == wfm) assert np.all(output[1]["time"] == np.linspace(0, N / SR, N)) - elem.addArray('2', wfm, SR, m1=m1) - elem.addArray('readout_channel', wfm, SR, m2=m2) + elem.addArray("2", wfm, SR, m1=m1) + elem.addArray("readout_channel", wfm, SR, m2=m2) elem.validateDurations() M = 2400 - wfm2 = np.linspace(0, M/SR, M) + wfm2 = np.linspace(0, M / SR, M) elem.addArray(3, wfm2, SR) with pytest.raises(ElementDurationError): @@ -126,8 +126,12 @@ def test_addArray(): @settings(max_examples=25, suppress_health_check=(HealthCheck.function_scoped_fixture,)) -@given(SR1=hst.integers(min_value=1,max_value=25*10**8), SR2=hst.integers(min_value = 1,max_value = 25*10**8), - N=hst.integers(min_value=2,max_value=25*10**6), M=hst.integers(min_value=2,max_value=25*10**6)) +@given( + SR1=hst.integers(min_value=1, max_value=25 * 10**8), + SR2=hst.integers(min_value=1, max_value=25 * 10**8), + N=hst.integers(min_value=2, max_value=25 * 10**6), + M=hst.integers(min_value=2, max_value=25 * 10**6), +) def test_invalid_durations(SR1, SR2, N, M): """ There are soooo many ways to have invalid durations, here @@ -139,7 +143,7 @@ def test_invalid_durations(SR1, SR2, N, M): elem = Element() bp = bb.BluePrint() - bp.insertSegment(0, ramp, (0, 0), dur=N/SR2) + bp.insertSegment(0, ramp, (0, 0), dur=N / SR2) bp.setSR(SR2) wfm = np.linspace(-1, 1, N) @@ -154,11 +158,11 @@ def test_invalid_durations(SR1, SR2, N, M): # differing durations bp1 = bb.BluePrint() - bp1.insertSegment(0, ramp, (0, 1), dur=N/SR1) + bp1.insertSegment(0, ramp, (0, 1), dur=N / SR1) bp1.setSR(SR1) bp2 = bb.BluePrint() - bp2.insertSegment(0, ramp, (0, 2), dur=M/SR1) + bp2.insertSegment(0, ramp, (0, 2), dur=M / SR1) bp2.setSR(SR1) elem = Element() @@ -173,13 +177,12 @@ def test_invalid_durations(SR1, SR2, N, M): def test_applyDelays(mixed_element): - delays = [1e-1, 0, 0] assert mixed_element.duration == 2.5 arrays_before = mixed_element.getArrays() - assert len(arrays_before[1]['wfm']) == 5000 + assert len(arrays_before[1]["wfm"]) == 5000 with pytest.raises(ValueError): mixed_element._applyDelays([-0.1, 3, 4]) @@ -191,31 +194,35 @@ def test_applyDelays(mixed_element): element._applyDelays(delays) arrays_after = element.getArrays() - assert len(arrays_after[1]['wfm']) == 5200 + assert len(arrays_after[1]["wfm"]) == 5200 assert mixed_element.duration == 2.5 assert element.duration == 2.6 - assert element._data[1]['blueprint'].length_segments == 4 - assert element._data[3]['blueprint'].length_segments == 2 + assert element._data[1]["blueprint"].length_segments == 4 + assert element._data[3]["blueprint"].length_segments == 2 ################################################## # Input validation -@pytest.mark.parametrize('improper_bp', [{1: 2}, 'blueprint', bb.BluePrint()]) +@pytest.mark.parametrize("improper_bp", [{1: 2}, "blueprint", bb.BluePrint()]) def test_input_fail1(improper_bp): elem = Element() with pytest.raises(ValueError): elem.addBluePrint(1, improper_bp) + ################################################## # Properties @settings(max_examples=25, suppress_health_check=(HealthCheck.function_scoped_fixture,)) -@given(SR=hst.integers(min_value=1,max_value=25*10**8), N=hst.integers(min_value=2,max_value=25*10**6)) +@given( + SR=hst.integers(min_value=1, max_value=25 * 10**8), + N=hst.integers(min_value=2, max_value=25 * 10**6), +) def test_points(SR, N): elem = Element() @@ -224,7 +231,7 @@ def test_points(SR, N): bp = bb.BluePrint() - bp.insertSegment(0, ramp, (0, 0), dur=N/SR) + bp.insertSegment(0, ramp, (0, 0), dur=N / SR) bp.setSR(SR) wfm = np.linspace(-1, 1, N) @@ -236,7 +243,7 @@ def test_points(SR, N): elem = Element() bp = bb.BluePrint() - bp.insertSegment(0, ramp, (0, 0), dur=N/SR) + bp.insertSegment(0, ramp, (0, 0), dur=N / SR) bp.setSR(SR) wfm = np.linspace(-1, 1, N) diff --git a/tests/test_forging.py b/tests/test_forging.py index ce28152c3..3eeb0f901 100644 --- a/tests/test_forging.py +++ b/tests/test_forging.py @@ -25,19 +25,17 @@ def sequence_maker(): """ def make_seq(seqlen, channels, SR): - seq = Sequence() seq.setSR(SR) - for pos in range(1, seqlen+1): - + for pos in range(1, seqlen + 1): elem = bb.Element() for chan in channels: bp = bb.BluePrint() - bp.insertSegment(-1, ramp, (0, 0), dur=20/SR) - bp.insertSegment(-1, ramp, (1, 1), dur=10/SR) - bp.insertSegment(-1, ramp, (0, 0), dur=5/SR) + bp.insertSegment(-1, ramp, (0, 0), dur=20 / SR) + bp.insertSegment(-1, ramp, (1, 1), dur=10 / SR) + bp.insertSegment(-1, ramp, (0, 0), dur=5 / SR) bp.setSR(SR) elem.addBluePrint(chan, bp) @@ -53,7 +51,7 @@ def _has_period(array: np.ndarray, period: int) -> bool: Check whether an array has a specific period """ try: - array = array.reshape((len(array)//period, period)) + array = array.reshape((len(array) // period, period)) except ValueError: return False @@ -65,19 +63,20 @@ def _has_period(array: np.ndarray, period: int) -> bool: return True -@given(SR=hst.integers(min_value=100, max_value=50*10**9), - ratio=hst.floats(min_value=1e-6, max_value=10)) +@given( + SR=hst.integers(min_value=100, max_value=50 * 10**9), + ratio=hst.floats(min_value=1e-6, max_value=10), +) def test_too_short_durations_rejected(SR, ratio): - # Any ratio larger than 1 will be rounded up by # _subelementBuilder to yield two points # (is that desired?) - shortdur = ratio*1/SR + shortdur = ratio * 1 / SR # however, since this is caluclated as dur*SR # it is possible for ratio > 1.5 but # shortdur*SR to be smaller due to fp roundoff # here we explicitly use shortdur*SR for that reason - round_tripped_ratio = shortdur*SR + round_tripped_ratio = shortdur * SR bp = bb.BluePrint() bp.setSR(SR) @@ -91,42 +90,41 @@ def test_too_short_durations_rejected(SR, ratio): def test_correct_periods(): - SR = 1e9 dur = 100e-9 freqs = [100e6, 200e6, 500e6] - periods = [int(SR/freq) for freq in freqs] + periods = [int(SR / freq) for freq in freqs] for freq, period in zip(freqs, periods): bp = bb.BluePrint() bp.insertSegment(0, sine, (freq, 1, 0, 0), dur=dur) bp.setSR(SR) - wfm = _subelementBuilder(bp, SR, [dur])['wfm'] + wfm = _subelementBuilder(bp, SR, [dur])["wfm"] assert _has_period(wfm, period) def test_correct_marker_times(): - SR = 100 bp = bb.BluePrint() - bp.insertSegment(-1, ramp, (0, 0), dur=1, name='A') - bp.insertSegment(-1, ramp, (0, 0), dur=1, name='B') - bp.insertSegment(-1, ramp, (0, 0), dur=1, name='C') + bp.insertSegment(-1, ramp, (0, 0), dur=1, name="A") + bp.insertSegment(-1, ramp, (0, 0), dur=1, name="B") + bp.insertSegment(-1, ramp, (0, 0), dur=1, name="C") bp.setSR(SR) - bp.setSegmentMarker('A', (0, 0.5), 1) - bp.setSegmentMarker('B', (-0.1, 0.25), 2) - bp.setSegmentMarker('C', (0.1, 0.25), 1) + bp.setSegmentMarker("A", (0, 0.5), 1) + bp.setSegmentMarker("B", (-0.1, 0.25), 2) + bp.setSegmentMarker("C", (0.1, 0.25), 1) forged_bp = _subelementBuilder(bp, SR, [1, 1, 1]) - m1 = forged_bp['m1'] + m1 = forged_bp["m1"] - assert (m1 == np.concatenate((np.ones(50), np.zeros(160), - np.ones(25), np.zeros(65)))).all() + assert ( + m1 == np.concatenate((np.ones(50), np.zeros(160), np.ones(25), np.zeros(65))) + ).all() def test_apply_filters_in_forging(sequence_maker): @@ -134,26 +132,26 @@ def test_apply_filters_in_forging(sequence_maker): Assign some filters, forge and assert that they were applied """ N = 5 - channels = [1, 2, 'my_channel'] + channels = [1, 2, "my_channel"] filter_orders = [1, 2, 3] SR = 1e9 seq = sequence_maker(N, channels, SR) for chan, order in zip(channels, filter_orders): - seq.setChannelFilterCompensation(chan, kind='HP', - order=order, f_cut=SR/10, - tau=None) + seq.setChannelFilterCompensation( + chan, kind="HP", order=order, f_cut=SR / 10, tau=None + ) forged_seq_bare = seq.forge(apply_filters=False) forged_seq_filtered = seq.forge(apply_filters=True) for chan, order in zip(channels, filter_orders): + wfm_bare = forged_seq_bare[1]["content"][1]["data"][chan]["wfm"] + expected = applyInverseRCFilter( + wfm_bare, SR, kind="HP", f_cut=SR / 10, order=order, DCgain=1 + ) - wfm_bare = forged_seq_bare[1]['content'][1]['data'][chan]['wfm'] - expected = applyInverseRCFilter(wfm_bare, SR, kind='HP', - f_cut=SR/10, order=order, DCgain=1) - - forged = forged_seq_filtered[1]['content'][1]['data'][chan]['wfm'] + forged = forged_seq_filtered[1]["content"][1]["data"][chan]["wfm"] assert np.all(expected == forged) diff --git a/tests/test_sequence.py b/tests/test_sequence.py index 6250fbbcd..5895b6b67 100644 --- a/tests/test_sequence.py +++ b/tests/test_sequence.py @@ -172,7 +172,12 @@ def squarepulse_baseelem(): def test_generate_arbitrary_waveform(): def two_sine(t, amp1, freq1, amp2, freq2, phase, off): - return amp1 * np.sin(2 * np.pi * freq1 * t) + amp2 * np.sin(2 * np.pi * freq2 * t + phase) + off + return ( + amp1 * np.sin(2 * np.pi * freq1 * t) + + amp2 * np.sin(2 * np.pi * freq2 * t + phase) + + off + ) + params = {"amp1": 1, "freq1": 1e6, "amp2": 1, "freq2": 2e6, "phase": 0, "off": 0} bp_func = bb.BluePrint() bp_func.setSR(1e9) @@ -332,6 +337,7 @@ def test_add_subsequence_raises(protosequence1, squarepulse_baseelem): ################################################## # Sequence properties + def test_duration(protosequence1, protosequence2): assert np.round(protosequence1.duration, 12) == 50e-6 assert np.round(protosequence2.duration, 12) == 75e-6 diff --git a/tests/test_subsequences.py b/tests/test_subsequences.py index 8c262b36d..d716b53cf 100644 --- a/tests/test_subsequences.py +++ b/tests/test_subsequences.py @@ -126,14 +126,13 @@ def noise_element(): @pytest.fixture def bp_element(): - dur = 100e-9 bp1 = bb.BluePrint() bp1.insertSegment(0, sine, (1e6, 10e-3, 0, 0), dur=dur) bp2 = bb.BluePrint() - bp2.insertSegment(0, sine, (2e6, 10e-3, 0, np.pi/2), dur=dur) + bp2.insertSegment(0, sine, (2e6, 10e-3, 0, np.pi / 2), dur=dur) bp3 = bb.BluePrint() bp3.insertSegment(0, sine, (3e6, 10e-3, 0, -1), dur=dur) @@ -143,7 +142,7 @@ def bp_element(): elem = bb.Element() for ch, bp in enumerate([bp1, bp2, bp3]): - elem.addBluePrint(ch+1, bp) + elem.addBluePrint(ch + 1, bp) return elem @@ -167,7 +166,6 @@ def master_sequence(subseq1, subseq2, bp_element, noise_element): def test_forge(master_sequence): - assert master_sequence.length_sequenceelements == 4 forged_seq = master_sequence.forge()