diff --git a/doc/source/examples/95DetailedSequencingWalkthrough.ipynb b/doc/source/examples/95DetailedSequencingWalkthrough.ipynb deleted file mode 100644 index fc59fecc7..000000000 --- a/doc/source/examples/95DetailedSequencingWalkthrough.ipynb +++ /dev/null @@ -1,747 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "# Detailed Sequencing Walkthrough\n", - "\n", - "__!!!!!!! CAUTION! This example provides outdated information and is only left for reference purposes !!!!!!!__\n", - "\n", - "Refer to [Instantiating Pulses](06CreatePrograms.ipynb) for up-to-date information about how to obtain pulses ready for execution from pulse templates.\n", - "\n", - "__!!!!!!!!!!!__\n", - "\n", - "This example will provide two step-by-step illustrations of the internals of the sequencing process. Note that this will involve some calls into the object structure to unveil internals which are not intended to be made in a productive use case and produce some very technical outputs. These are broken down and explained in detail where necessary.\n", - "\n", - "## Example 1 (Software-Evaluated Loop Condition)\n", - "\n", - "In the first example, we will emulate the behaviour of a `RepetitonPulseTemplate` to repeat a `TablePulseTemplate` for a fixed number of times using a `LoopPulseTemplate` with a `SoftwareCondition`. We have done so already in [the example for conditional execution](06ConditionalExecution.ipynb) but here we will explore the sequencing process in detail. The definitions of the classes are the following (resulting in 2 repetitions):" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from qupulse.pulses import TablePT, Sequencer\n", - "from qupulse.pulses.loop_pulse_template import WhileLoopPulseTemplate\n", - "from qupulse.pulses.conditions import SoftwareCondition\n", - "\n", - "# define a table pulse template which we want to repeat (including a parameter)\n", - "entries = [(1, 'foo', 'linear'), (3, 'foo'), (4, 0, 'linear')]\n", - "table_template = TablePT({0: entries})\n", - "# define a software condition will evaluate to true as long as the loop counter is less than 5 and false afterwards\n", - "repeat_condition = SoftwareCondition(lambda x: x < 2) # it will never require an interruption of the sequencing process\n", - "\n", - "# define a loop template consisting of the table template as body and a condition identified by 'rcon'\n", - "loop_template = WhileLoopPulseTemplate('rcon', table_template)\n", - "\n", - "# provide sequencing mappings: condition 'rcon' -> repeat_condition and parameter 'foo' -> 2\n", - "conditions = {'rcon': repeat_condition}\n", - "parameters = {'foo': 2}\n", - "\n", - "# create a Sequencer instance and push our loop template on the sequencing stack with the corresponding mappings \n", - "s = Sequencer()\n", - "s.push(loop_template, parameters, conditions)\n", - "\n", - "# store references to the main instruction block and the corresponding sequencing stack\n", - "main_block = s._Sequencer__main_block\n", - "sequencing_stack = s._Sequencer__sequencing_stacks[main_block]" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[(, {'foo': }, {'rcon': }, {}, {0: 0})]\n" - ] - } - ], - "source": [ - "print(sequencing_stack) # print the current sequencing stack for the main block" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you can see in the dump of the sequencing stack of the main instruction block, there is currently one item on the stack, which a tuple consisting of our `LoopPulseTemplate` `loop_template` and the mappings `parameters` and `conditions`. The following figure illustrates the current content sequencing stack.\n", - "\n", - "![The sequencing stack after pushing `loop_template`](img/walkthrough1_01.png)\n", - "\n", - "Running `Sequencer.build()` would run the entire sequencing process, resulting in the desired instruction sequence. However, since we want to understand the process itself, we will perform the necessary steps ourselves by manually calling the corresponding functions. We now translate the topmost (and only) stack item:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# get the topmost item from the sequencing stack\n", - "(template, params, conds, window_mapping, channel_mapping) = sequencing_stack[-1]\n", - "# remove template from stack and translate it it does not require a stop\n", - "if not template.requires_stop(params, conds):\n", - " sequencing_stack.pop()\n", - " template.build_sequence(s, params, conds, window_mapping, channel_mapping, main_block)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `build_sequence` method looks up the condition identified by 'rcon' in the conditions map `conditions` which is our `repeat_condition` object defined above. It then invokes the `build_sequence_loop` method of this object. Being a `SoftwareCondition` object, it evaluates its evaluation function, which returns true, and thus adds the body, our `table_template` to the sequencing stack. Since the loop condition must be evaluated again after the loop body was run, also the `loop_template` is pushed to the stack again. Thus, the stack now looks as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[(, {'foo': }, {'rcon': }, {}, {0: 0}), (, {'foo': }, {'rcon': }, {}, {0: 0})]\n" - ] - } - ], - "source": [ - "print(sequencing_stack) # print the current sequencing stack for the main block" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![The sequencing stack after translating `loop_template`](img/walkthrough1_02.png)\n", - "\n", - "Note that no waveforms or instructions have been generated so far, i.e., the main instruction block is empty:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "print(main_block.instructions) # print all instructions in the main block" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Sequencer` would now enter the next iteration, i.e., pop and translate the topmost element from the stack." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "# get the topmost item from the sequencing stack\n", - "(template, params, conds, window_mapping, channel_mapping) = sequencing_stack[-1]\n", - "# remove template from stack and translate it it does not require a stop\n", - "if not template.requires_stop(params, conds):\n", - " sequencing_stack.pop()\n", - " template.build_sequence(s, params, conds, window_mapping, channel_mapping, main_block)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This time, our `table_template`, that is, the body of the loop, is at the top. It's translation via `build_sequence()` looks up the parameter value for 'foo', generates a waveform and inserts a corresponding instruction in the main block:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, ]\n" - ] - } - ], - "source": [ - "print(main_block.instructions) # print all instructions in the main block" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since we've successfully processed the `table_template` item on the sequencing stack, we are left with a `loop_template` item. That means, the stack looks just like in the beginning (refer to Figure 1)." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[(, {'foo': }, {'rcon': }, {}, {0: 0})]\n" - ] - } - ], - "source": [ - "print(sequencing_stack)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will fetch it from the stack and translate it. Since the loop counter in the `SoftwareCondition` object is currently 1, it will still evaluate to true, meaning that the loop continues, i.e., the body template and the loop template are again pushed to the stack (cf. Figure 2)." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[(, {'foo': }, {'rcon': }, {}, {0: 0}), (, {'foo': }, {'rcon': }, {}, {0: 0})]\n" - ] - } - ], - "source": [ - "# get the topmost item from the sequencing stack\n", - "(template, params, conds, window_mapping, channel_mapping) = sequencing_stack[-1]\n", - "# remove template from stack and translate it it does not require a stop\n", - "if not template.requires_stop(params, conds):\n", - " sequencing_stack.pop()\n", - " template.build_sequence(s, params, conds, window_mapping, channel_mapping, main_block)\n", - " \n", - "print(sequencing_stack)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Proceeding as before, we translate the topmost element, which is again the loop body `table_template`. This results in the expected `EXECInstruction` and a stack in which the `loop_template` remains for reevaluation." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[(, {'foo': }, {'rcon': }, {}, {0: 0})]\n" - ] - } - ], - "source": [ - "# get the topmost item from the sequencing stack\n", - "(template, params, conds, window_mapping, channel_mapping) = sequencing_stack[-1]\n", - "# remove template from stack and translate it it does not require a stop\n", - "if not template.requires_stop(params, conds):\n", - " sequencing_stack.pop()\n", - " template.build_sequence(s, params, conds, window_mapping, channel_mapping, main_block)\n", - " \n", - "print(sequencing_stack)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our main instruction block now contains two `EXECInstruction`s:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, , , ]\n" - ] - } - ], - "source": [ - "print(main_block.instructions) # print all instructions in the main block" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "We are left with the `loop_template` on the stack, which we will translate in the following. However, this time the `repeat_condition` will evaluate to false, meaning that neither body nor loop template are pushed to the stack. We are done with the loop." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[]\n" - ] - } - ], - "source": [ - "# get the topmost item from the sequencing stack\n", - "(template, params, conds, window_mapping, channel_mapping) = sequencing_stack[-1]\n", - "# remove template from stack and translate it it does not require a stop\n", - "if not template.requires_stop(params, conds):\n", - " sequencing_stack.pop()\n", - " template.build_sequence(s, params, conds, window_mapping, channel_mapping, main_block)\n", - " \n", - "print(sequencing_stack)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the final ouput of the sequencing process, the `InstructionBlock` the `Sequencer` uses internally is copied into an immutable representation such that no outside changes influence the internal state of the `Sequencer`:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, , , , ]\n" - ] - } - ], - "source": [ - "from qupulse._program.instructions import ImmutableInstructionBlock\n", - "return_block = ImmutableInstructionBlock(main_block)\n", - "print([instruction for instruction in return_block])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that, by using the iterable interface of the returned block, we automatically obtain a finishing `STOPInstruction` (as compared to iterating over `return_block.instructions`). Now we are done." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True\n" - ] - } - ], - "source": [ - "print(s.has_finished()) # are we done?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We have explored what happens internally when we invoke `Sequencer.build()` on our `loop_template`. In a productive use case, we can let `Sequencer` handle all of this and get the same result (apart from memory addresses of the involved python objects):" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, , , , ]\n" - ] - } - ], - "source": [ - "s = Sequencer()\n", - "repeat_condition = SoftwareCondition(lambda x: x < 2) # it will never require an interruption of the sequencing process\n", - "conditions = {'rcon': repeat_condition}\n", - "s.push(loop_template, parameters, conditions)\n", - "instructions = s.build()\n", - "print([instruction for instruction in instructions])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Example 2 (Hardware Evaluated Branch Nested In Loop)\n", - "\n", - "In this example we want to look into hardware-based branching evaluation based using the `HardwareCondition` class and how `InstructionBlocks` are created and handled during the `Sequencing` process. The pulse we want to translate is a loop which contains a branch template (if-else-construct) which in turn contains table pulse templates:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'BranchPulseTemplate' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 20\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 21\u001b[0m \u001b[1;31m# the branch pulse template\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 22\u001b[1;33m \u001b[0mbranch_template\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mBranchPulseTemplate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'bcon'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mpos_template\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mneg_template\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 23\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 24\u001b[0m \u001b[1;31m# the loop pulse template\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mNameError\u001b[0m: name 'BranchPulseTemplate' is not defined" - ] - } - ], - "source": [ - "from qupulse.pulses import TablePT, Sequencer\n", - "from qupulse.pulses.loop_pulse_template import WhileLoopPulseTemplate\n", - "from qupulse.pulses.conditions import HardwareCondition\n", - "\n", - "# two table pulse templates for the alternative paths of the branch pulse template\n", - "# they differ in their interpolation behaviour (jump vs linear ramp)\n", - "pos_template = TablePT({\n", - " 0: [(1, 'foo', 'linear'),\n", - " (3, 'foo'),\n", - " (4, 0, 'linear')]\n", - "})\n", - "\n", - "neg_template = TablePT({\n", - " 0: [(1, 'foo'),\n", - " (3, 'foo'),\n", - " (4, 0)]\n", - "})\n", - "\n", - "parameters = {'foo': 2}\n", - "\n", - "# the branch pulse template\n", - "branch_template = BranchPulseTemplate('bcon', pos_template, neg_template)\n", - "\n", - "# the loop pulse template\n", - "loop_template = WhileLoopPulseTemplate('lcon', branch_template)\n", - "\n", - "\n", - "# for this example: Introduce a trigger that can be identified by a name\n", - "class NamedTrigger(Trigger):\n", - " def __init__(self, name: str) -> None:\n", - " self.name = name\n", - " \n", - " def __str__(self) -> str:\n", - " return \"Trigger '{}'\".format(self.name)\n", - "\n", - "# create HardwareCondition objects for branch and loop\n", - "branch_condition = HardwareCondition(NamedTrigger(\"branch_trigger\"))\n", - "loop_condition = HardwareCondition(NamedTrigger(\"loop_trigger\"))\n", - "\n", - "# mapping of identifiers to conditions\n", - "conditions = {'bcon': branch_condition, 'lcon': loop_condition}\n", - "\n", - "# create a Sequencer instance and push our loop template on the sequencing stack with the corresponding mappings \n", - "s = Sequencer()\n", - "s.push(loop_template, parameters, conditions)\n", - "\n", - "# store references to the main instruction block and the corresponding sequencing stack\n", - "main_block = s._Sequencer__main_block\n", - "main_sequencing_stack = s._Sequencer__sequencing_stacks[main_block]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The sequencing stack now contains a single entry, namely the tuple containing our 'loop_template' and the mappings 'parameters' and 'conditions':" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(main_sequencing_stack)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![The initial sequencing stack for example 2](img/walkthrough2_01.png)\n", - "\n", - "Entering the sequencing process, we translate the topmost element as before:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# get the topmost item from the sequencing stack\n", - "(template, params, conds, window_mapping, channel_mapping) = main_sequencing_stack[-1]\n", - "# remove template from stack and translate it it does not require a stop\n", - "if not template.requires_stop(params, conds):\n", - " main_sequencing_stack.pop()\n", - " template.build_sequence(s, params, conds, window_mapping, channel_mapping, main_block)\n", - " \n", - "print(main_sequencing_stack)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Surprisingly at a first glance, the sequencing stack of the main instruction block is empty afterwards although we are far from being done with the sequencing process. What happended here is that the call to `LoopPulseTemplate`s `build_sequence()` method resulted in a call to `build_sequence_loop` of the corresponding condition object `loop_condition`. This is of type `HardwareConditon`, meaning that all possible execution paths must be translated into a hardware understandable format. Thus, a new `InstructionBlock` was instantiated into which the body of the loop will be sequenced. Accordingly, the remaining templates which represent the loops body are pushed to the specific sequencing stack of this new instruction block. In the main block we will simply find a `CJMPInstruction` (conditional jump instruction) to the new block." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(main_block.instructions)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# obtain a reference to the new InstructionBlock representing the body of the loop\n", - "loop_body_block = main_block._InstructionBlock__instruction_list[0].target.block\n", - "loop_body_stack = s._Sequencer__sequencing_stacks[loop_body_block]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The contents of the sequencing stacks are the following:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(loop_body_stack) # print the sequencing stack for the loop body block" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Sequencing stacks after translating the loop template](img/walkthrough2_02.png)\n", - "\n", - "`Sequencer` continues the sequencing process until it cannot proceed for any instruction block currently under construction. Thus, although the stack for the main block is empty, we continue with the loop body block:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# get the topmost item from the sequencing stack\n", - "(template, params, conds, window_mapping, channel_mapping) = loop_body_stack[-1]\n", - "# remove template from stack and translate it it does not require a stop\n", - "if not template.requires_stop(params, conds):\n", - " loop_body_stack.pop()\n", - " template.build_sequence(s, params, conds, window_mapping, channel_mapping, loop_body_block)\n", - " \n", - "print(loop_body_stack)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since we translated a `BranchLoopTemplate` with a `HardwareConditon` we end up with two new instructions blocks, one for the if-branch and one for the else-branch, with separate sequencing stacks. We also obtain corresponding jump instructions in the loop body block: A conditional jump into the if-branch, performed if the condition is fulfulled followed by an unconditional goto into the else-branch, if the conditional jump does not occur, i.e., the condition is not fullfilled." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(loop_body_block.instructions)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# get references to if and else branches\n", - "if_branch_block = loop_body_block._InstructionBlock__instruction_list[0].target.block\n", - "else_branch_block = loop_body_block._InstructionBlock__instruction_list[1].target.block\n", - "if_branch_stack = s._Sequencer__sequencing_stacks[if_branch_block]\n", - "else_branch_stack = s._Sequencer__sequencing_stacks[else_branch_block]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The stacks now look as follows:\n", - "\n", - "![Sequencing stacks after translating the branch template](img/walkthrough2_03.png)\n", - "\n", - "The table pulse templates `pos_template` and `neg_template` are translated in the usual manner, resulting in an `EXECInstruction` in the respective instruction blocks:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# translate if-branch stack\n", - "(template, params, conds, window_mapping, channel_mapping) = if_branch_stack[-1]\n", - "if not template.requires_stop(params, conds):\n", - " if_branch_stack.pop()\n", - " template.build_sequence(s, params, conds, window_mapping, channel_mapping, if_branch_block)\n", - " \n", - "# translate else-branch stack\n", - "(template, params, conds, window_mapping, channel_mapping) = else_branch_stack[-1]\n", - "if not template.requires_stop(params, conds):\n", - " else_branch_stack.pop()\n", - " template.build_sequence(s, params, conds, window_mapping, channel_mapping, else_branch_block)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Afterwards, all stacks are empty" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(main_sequencing_stack)\n", - "print(loop_body_stack)\n", - "print(if_branch_stack)\n", - "print(else_branch_stack)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and we are left with four instruction blocks, two of which contains `EXECInstructions` while the rest only specifies control flow, that is, (conditional) jumps into other blocks.\n", - "\n", - "Again, we convert the interal `InstructionBlock` objects into immutable ones before we return them to protect the internal state of `Sequencer` from outside manipulation. Note that we retain the hierarchy between the different instructon blocks we have created and do not convert it into a single sequence with internal jumps to allow hardware device drivers to identify subsequences more easily. The dictionary `immutable_lookup` is given as an optional second parameter to the constructor of `ImmutableInstructionBlock` which will fill it with entries of the type `mutable block` -> `immutable block` to allow us to look up the immutable representation of each of our blocks." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "immutable_lookup = dict()\n", - "ImmutableInstructionBlock(main_block, immutable_lookup)\n", - "print(\"Main Block:\")\n", - "print([instruction for instruction in immutable_lookup[main_block]])\n", - "print(\"\\nLoop Body Block:\")\n", - "print([instruction for instruction in immutable_lookup[loop_body_block]])\n", - "print(\"\\nIf Branch Block:\")\n", - "print([instruction for instruction in immutable_lookup[if_branch_block]])\n", - "print(\"\\nElse Branch Block:\")\n", - "print([instruction for instruction in immutable_lookup[else_branch_block]])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " In an illustration, the blocks and the jumps between them look like this:\n", - "\n", - "![Interconnected instruction block](img/walkthrough2_04.png)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(s.has_finished()) # really done?" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [default]", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.0" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/doc/source/examples/97ConditionalExecution.ipynb b/doc/source/examples/97ConditionalExecution.ipynb deleted file mode 100644 index 976247be6..000000000 --- a/doc/source/examples/97ConditionalExecution.ipynb +++ /dev/null @@ -1,169 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Conditional Execution\n", - "\n", - "__!!!!!!! CAUTION! This example provides outdated information and is only left for reference purposes !!!!!!!__\n", - "\n", - "Conditional exeuction is currently not supported (but we plan to get it back some time in the future).\n", - "\n", - "__!!!!!!!!!!!!!!__\n", - "\n", - "qupulse is desinged to support conditional execution of pulse (segments). This allows pulse designers to play back different waveforms depending on, e.g., environment data or state measurements of the quantum dot. Conditional execution may be implemented via trigger-based jumps to instructions directly on the playback device or emulated in software by choosing which pulse to send to the hardware for playback, if the hardware itself does not support branching.\n", - "\n", - "Since the decision whether a condition will be evaluated software- or hardware-based depends on the hardware setup of the experiment and not on the pulse template, qupulse relies on an indirection scheme for conditions which is similar to the handling of parameters, as you will see in the following.\n", - "\n", - "qupulse offers two `PulseTemplate` subclasses for conditional execution: `LoopPulseTemplate` and `BranchPulseTemplate`. \n", - "\n", - "`LoopPulseTemplate` takes an identifier for a condition and a subtemplate which is repeated as long as the condition evaluates to `True`. Let's assume that we want to construct a pulse that waits until some initialization is completed using a `TablePulseTemplate` representing a zero-pulse of length 5 and a `LoopPulseTemplate`:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from qupulse.pulses import TablePT\n", - "from qupulse.pulses.loop_pulse_template import WhileLoopPulseTemplate\n", - "\n", - "wait_template = TablePT({0: [(5, 0)]})\n", - "\n", - "loop_template = WhileLoopPulseTemplate('initialization_in_progress', wait_template)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`loop_template` is now configured to evaluate a condition called 'initialization_in_progress'. How this condition is implemented needs not to be specified to declare pulse templates." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "We will now look into the actual implementation of conditions. The abstract interface of conditions is the `Condition` class. As mentioned in the beginning, conditions can be evaluated software- and hardware-based. The classes `SoftwareCondition` and `HardwareCondition` represent this in qupulse. Note that these classes don't do the actual evaluation but encapsulate it against the `Sequencer` and are used to generate correct sequences for both cases. Instances of `Condition`s are passed directly into the `Sequencer` and mapped to the `PulseTemplate`s via the identifier, similar to parameters.\n", - "\n", - "## Software-Based Conditions\n", - "`SoftwareCondition` takes a callback function which is called to evaluate the condition (and thus must return a boolean value). During the sequencing process, this function will be called. If the return value is `True`, the subtemplate will be included in the instruction sequence and another evaluation will be made until the return value is `False`. The generated instruction sequence will thus contain a number of repetitions of the subtemplate but no actual jumping instructions, the loop is essentially unrolled. The callback function is passed an integer value indicating the current iteration of the evaluation.\n", - "\n", - "As an example, we could use this to repeat the `wait_template` a fixed number of times, say 5, as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, , , , , , , , , , ]\n" - ] - } - ], - "source": [ - "from qupulse.pulses import Sequencer\n", - "from qupulse.pulses.conditions import SoftwareCondition\n", - "\n", - "constant_repeat_condition = SoftwareCondition(lambda x: x < 5)\n", - "conditions = {'initialization_in_progress': constant_repeat_condition}\n", - "\n", - "s = Sequencer()\n", - "parameters = {}\n", - "s.push(loop_template, parameters, conditions)\n", - "instructions = s.build()\n", - "print([instruction for instruction in instructions])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We obtain an instruction sequence that repeats an execution instruction (for the `wait_template` waveform) five times. This is, of course, a very simple example. The callback function passed into the `SoftwareCondition` instance will more likely evaluate some measured data. Since this might not always be available in the sequencing pass, the callback may return `None`, which will interrupt the sequencing process similar to the [`requires_stop` method of the `Parameter` class](05Parameters.ipynb)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Hardware-Based Conditions\n", - "Since software-based evaluation of conditions is slow and might interrupt the sequencing process, it might not always be applicable. If supported by the hardware, hardware-based condition evaluation is preferrable. For the sequencing process, this is represented by instances of `HardwareCondition`, which only take some identifier of the hardware trigger. This must first be obtained from the hardware *currently not implemented* and is embedded in the generated instruction sequence which will contain jump instructions.\n", - "\n", - "Assuming we have a hardware setup with a trigger that fires continuously until temperature threshold is reached and we want our pulse from above to repeat as long as the trigger fires. We can realize this as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "main instruction sequence:\n", - "[, ]\n", - "\n", - "looped instruction sequence:\n", - "[, , ]\n" - ] - } - ], - "source": [ - "from qupulse.pulses.conditions import HardwareCondition, Trigger\n", - "\n", - "# stub representation for the trigger which must be obtained from the hardware in a real use case\n", - "temperature_trigger = Trigger()\n", - "\n", - "temperature_trigger_condition = HardwareCondition(temperature_trigger)\n", - "conditions = {'initialization_in_progress': temperature_trigger_condition}\n", - "\n", - "s = Sequencer()\n", - "parameters = {}\n", - "s.push(loop_template, parameters, conditions)\n", - "instructions = s.build()\n", - "\n", - "# output the generated program\n", - "print(\"main instruction sequence:\")\n", - "print([instruction for instruction in instructions])\n", - "print(\"\\nlooped instruction sequence:\")\n", - "print([instruction for instruction in instructions[0].target.block])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you can see in the output, the sequencing process now procudes instructions that perform conditional jumps and returns with goto instructions. The details are omitted here, suffice it to say that the trigger is embedded in the conditional jump instruction and this sequence will loop on the hardware as long as the trigger fires." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [default]", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.0" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/doc/source/examples/98Parameters.ipynb b/doc/source/examples/98Parameters.ipynb deleted file mode 100644 index 89b66ccb5..000000000 --- a/doc/source/examples/98Parameters.ipynb +++ /dev/null @@ -1,1795 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# More Sophisticated Parameters\n", - "\n", - "__!!!!!!! CAUTION! This example provides outdated information and is only left for reference purposes !!!!!!!__\n", - "\n", - "Due to a change to the entire process of how pulse templates are turned into pulses, this example largely obsolete.\n", - "\n", - "__!!!!!!!!!!!!!!__\n", - "\n", - "This example assumes that you are familiar with the sequencing process. If you need a refresher on this, have a look at [The Sequencing Process: Obtaining Pulse Instances From Pulse Templates](04Sequencing.ipynb) first.\n", - "\n", - "*Attention/Broken: During the creation of this example some implementation errors were found in qupulse. Due to time constraints, these were not fixed immediately, leaving this example to be slightly flawed. However, in order to demonstrate the underlying principles, this example is published in its current form. Annotations like this mark where the current behavior of qupulse diverges from the intended one.*\n", - "\n", - "So far we have only considered constant parameter values. Now assume that we need to derive the value for a parameter based on some measurements we've made during the execution of previous parts of a composite pulse. For example, let the pulse we want to execute be constructed as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " if (mpl.ratio != 1) {\n", - " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", - " }\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " fig.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var backingStore = this.context.backingStorePixelRatio ||\n", - "\tthis.context.webkitBackingStorePixelRatio ||\n", - "\tthis.context.mozBackingStorePixelRatio ||\n", - "\tthis.context.msBackingStorePixelRatio ||\n", - "\tthis.context.oBackingStorePixelRatio ||\n", - "\tthis.context.backingStorePixelRatio || 1;\n", - "\n", - " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width * mpl.ratio);\n", - " canvas.attr('height', height * mpl.ratio);\n", - " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('');\n", - " button.click(method_name, toolbar_event);\n", - " button.mouseover(tooltip, toolbar_mouse_event);\n", - " nav_element.append(button);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var button = $('');\n", - " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", - " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", - " buttongrp.append(button);\n", - " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", - " titlebar.prepend(buttongrp);\n", - "}\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(el){\n", - " var fig = this\n", - " el.on(\"remove\", function(){\n", - "\tfig.close_ws(fig, {});\n", - " });\n", - "}\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(el){\n", - " // this is important to make the div 'focusable\n", - " el.attr('tabindex', 0)\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " }\n", - " else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._key_event_extra = function(event, name) {\n", - " var manager = IPython.notebook.keyboard_manager;\n", - " if (!manager)\n", - " manager = IPython.keyboard_manager;\n", - "\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which == 13) {\n", - " this.canvas_div.blur();\n", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\n", - " }\n", - "}\n", - "\n", - "mpl.figure.prototype.handle_save = function(fig, msg) {\n", - " fig.ondownload(fig, null);\n", - "}\n", - "\n", - "\n", - "mpl.find_output_cell = function(html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] == html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel != null) {\n", - " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%matplotlib notebook\n", - "from qupulse.pulses import TablePT, SequencePT\n", - "from qupulse.pulses.plotting import plot\n", - "\n", - "init_template = TablePT(entries={0: [(2, 5),\n", - " (4, -5),\n", - " (6, 0),\n", - " (8, 0)]})\n", - "\n", - "dependent_template = TablePT(entries={0: [(2, 0),\n", - " (5, 'v', 'linear'),\n", - " (10, 0, 'linear')]},\n", - " measurements=[('A', 0, 2),\n", - " ('B', 4, 0)])\n", - "\n", - "sequence_template = SequencePT((init_template, {}),\n", - " (dependent_template, {'v': 'v'}),\n", - " (init_template, {}))\n", - "\n", - "plot(sequence_template, {'v': 1}, sample_rate=100)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we want to let the value of parameter `v` depend somehow on the measurement we make between time 8 and 12 (assuming we have a way to obtain measurement data, which is currently not the case (work in progress)). Thus we need to execute the first part of the pulse, then compute the parameter value and execute the remainder. We can do so be encapsulating the computation of the parameter value in a custom subclass of `Parameter`. Assuming, for simplicity, that we have some `measurement_manager` object which we can query whether or not the measurement has been made (`is_measurement_available()`) and what data was obtained (`get_data()`) and that the value of `v` shall simply be twice the measured data, this subclass might look like this:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from qupulse.pulses.parameters import Parameter\n", - "\n", - "class MeasurementDependentParameter(Parameter):\n", - " \n", - " def __init__(self, measurement_manager) -> None:\n", - " self.measurement_manager = measurement_manager\n", - " \n", - " @property\n", - " def requires_stop(self) -> bool:\n", - " return not self.measurement_manager.is_measurement_available()\n", - " \n", - " def get_value(self) -> float:\n", - " return 2*(self.measurement_manager.get_data())\n", - " \n", - " def get_serialization_data(self, serializer):\n", - " raise NotImplementedError()\n", - " \n", - " @staticmethod\n", - " def deserialize(serializer):\n", - " raise NotImplementedError()\n", - " \n", - " def __hash__():\n", - " pass" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We overwrite the abstract property `requires_stop` and the abstract method `get_value` of the `Parameter` base class. `requires_stop` is used to indicate to the `Sequencer` whether the `Parameter` object can currently be evaluated or whether the sequencing process has to be interrupted. Our `MeasurementDependentParameter` will return `True` if no measurement data is available (in contrast, the `ConstantParameter` - which internally represents any float value passed in - always returns `False`). The `get_value` method returns the parameter value. It is only called if `requires_stop` is false. In the `MesaurementDependentParameter` class, we assume that the measured data is a single float and that we simple want to multiply it by 2 as the parameter's value. The other two methods, `get_serialization_data` and `deserialize` also must be overwritten since each `Parameter` implements the [`Serializable` interface](03Serialization.ipynb). However, we just raise an exception here since these methods are not relevant in this example.\n", - "\n", - "We would then set up our pulse for execution like as in the following snippet (including a stub implementation of a `MeasurementManager` just for demonstration purposes):" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "from qupulse.pulses import Sequencer\n", - "\n", - "# We define a stub for the measurement manager here only for illustration purposes.\n", - "class MeasurementManager:\n", - " def __init__(self, sequencer: Sequencer) -> None:\n", - " self.sequencer = sequencer\n", - " self.is_available = False\n", - " \n", - " def is_measurement_available(self) -> bool:\n", - " return self.is_available\n", - " \n", - " def get_data(self) -> float:\n", - " return 3\n", - "\n", - "sequencer = Sequencer()\n", - "measurement_manager = MeasurementManager(sequencer)\n", - "parameter = MeasurementDependentParameter(measurement_manager)\n", - "\n", - "sequencer.push(init_template)\n", - "sequencer.push(dependent_template, {'v': parameter})\n", - "sequencer.push(init_template)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `MeasurementManager.is_measurement_available` stub will simply return the value to which we have set the `is_available` member variable of the class.\n", - "\n", - "When we invoke `Sequencer.build`, for each template on the sequencing stack it first queries whether or not all parameters can be evaluated. If any of them returns `True` via the `requires_stop` method, the sequencing process will be interrupted.\n", - "In our example, `Sequencer` will first proceed through the first two subtemplates of `sequence_template`. When it arrives at `dependent_template`, it will stop:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, , ]\n" - ] - } - ], - "source": [ - "first_block = sequencer.build()\n", - "print([instruction for instruction in first_block])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you can see in the output above, only two executions instructions are generated, one for each `TablePulseTemplate` instance before the `dependent_template`. Let us now switch the `is_available` variable of the `MeasurementManager` instance to `True`, simulating that we've obtained some measurement result, and invoke the `Sequencer` again:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, , , , , , ]\n" - ] - } - ], - "source": [ - "measurement_manager.is_available = True\n", - "second_block = sequencer.build()\n", - "print([instruction for instruction in second_block])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We have now obtained the complete sequence with one execution instruction for each `TablePulseTemplate`.\n", - "*Attention/Broken: Currently this is incorrect behavior: We would want to only get the remainder of the pulse in the second call since we wouldn't want to execute the first part of the pulse again. Needs fixing ([issue 105](https://github.com/qutech/qc-toolkit/issues/105)).*" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [default]", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.0" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/qupulse/_program/_loop.py b/qupulse/_program/_loop.py index 741a33f43..a31ff7fd8 100644 --- a/qupulse/_program/_loop.py +++ b/qupulse/_program/_loop.py @@ -7,16 +7,16 @@ import numpy as np +from qupulse._program.waveforms import Waveform + from qupulse.utils.types import ChannelID, TimeType -from qupulse._program.instructions import AbstractInstructionBlock, EXECInstruction, REPJInstruction, GOTOInstruction,\ - STOPInstruction, CHANInstruction, Waveform, MEASInstruction, Instruction from qupulse.utils.tree import Node, is_tree_circular from qupulse.utils.types import MeasurementWindow from qupulse.utils import is_integer from qupulse._program.waveforms import SequenceWaveform, RepetitionWaveform -__all__ = ['Loop', 'MultiChannelProgram', 'make_compatible', 'MakeCompatibleWarning'] +__all__ = ['Loop', 'make_compatible', 'MakeCompatibleWarning'] class Loop(Node): @@ -340,145 +340,6 @@ def __init__(self, channel_sets): self.channel_sets = channel_sets -class MultiChannelProgram: - def __init__(self, instruction_block: Union[AbstractInstructionBlock, Loop], channels: Iterable[ChannelID] = None): - """Channels with identifier None are ignored.""" - self._programs = dict() - if isinstance(instruction_block, AbstractInstructionBlock): - self._init_from_instruction_block(instruction_block, channels) - elif isinstance(instruction_block, Loop): - assert channels is None - self._init_from_loop(loop=instruction_block) - else: - raise TypeError('Invalid program type', type(instruction_block), instruction_block) - - for program in self.programs.values(): - program.cleanup() - - def _init_from_loop(self, loop: Loop): - first_waveform = next(loop.get_depth_first_iterator()).waveform - - assert first_waveform is not None - - self._programs[frozenset(first_waveform.defined_channels)] = loop - - def _init_from_instruction_block(self, instruction_block, channels): - if channels is None: - def find_defined_channels(instruction_list): - for instruction in instruction_list: - if isinstance(instruction, EXECInstruction): - yield instruction.waveform.defined_channels - elif isinstance(instruction, REPJInstruction): - yield from find_defined_channels( - instruction.target.block.instructions[instruction.target.offset:]) - elif isinstance(instruction, GOTOInstruction): - yield from find_defined_channels(instruction.target.block.instructions[instruction.target.offset:]) - elif isinstance(instruction, CHANInstruction): - yield itertools.chain(*instruction.channel_to_instruction_block.keys()) - elif isinstance(instruction, STOPInstruction): - return - elif isinstance(instruction, MEASInstruction): - pass - else: - raise TypeError('Unhandled instruction type', type(instruction)) - - try: - channels = next(find_defined_channels(instruction_block.instructions)) - except StopIteration: - raise ValueError('Instruction block has no defined channels') - else: - channels = set(channels) - - channels = frozenset(channels - {None}) - - root = Loop() - stacks = {channels: (root, [((), deque(instruction_block.instructions))])} - - while len(stacks) > 0: - chans, (root_loop, stack) = stacks.popitem() - try: - self._programs[chans] = MultiChannelProgram.__split_channels(chans, root_loop, stack) - except ChannelSplit as split: - for new_channel_set in split.channel_sets: - assert (new_channel_set not in stacks) - assert (chans.issuperset(new_channel_set)) - - stacks[new_channel_set] = (root_loop.copy_tree_structure(), deepcopy(stack)) - - def repeat_measurements(child_loop, rep_count): - duration_float = float(child_loop.duration) - if child_loop._measurements: - for r in range(rep_count): - for name, begin, length in child_loop._measurements: - yield (name, begin+r*duration_float, length) - - @property - def programs(self) -> Dict[FrozenSet[ChannelID], Loop]: - return self._programs - - @property - def channels(self) -> Set[ChannelID]: - return set(itertools.chain(*self._programs.keys())) - - @staticmethod - def __split_channels(channels: FrozenSet[ChannelID], - root_loop: Loop, - block_stack: List[Tuple[Tuple[int, ...], - deque]]) -> Loop: - while block_stack: - current_loop_location, current_instruction_block = block_stack.pop() - current_loop = root_loop.locate(current_loop_location) - - while current_instruction_block: - instruction = current_instruction_block.popleft() - - if isinstance(instruction, EXECInstruction): - if not instruction.waveform.defined_channels.issuperset(channels): - raise Exception(instruction.waveform.defined_channels, channels) - current_loop.append_child(waveform=instruction.waveform) - - elif isinstance(instruction, REPJInstruction): - if current_instruction_block: - block_stack.append((current_loop_location, current_instruction_block)) - - current_loop.append_child(repetition_count=instruction.count) - block_stack.append( - (current_loop[-1].get_location(), - deque(instruction.target.block[instruction.target.offset:-1])) - ) - break - - elif isinstance(instruction, CHANInstruction): - if channels in instruction.channel_to_instruction_block.keys(): - # push to front - new_instruction_ptr = instruction.channel_to_instruction_block[channels] - new_instruction_list = [*new_instruction_ptr.block[new_instruction_ptr.offset:-1]] - current_instruction_block.extendleft(new_instruction_list) - - else: - block_stack.append((current_loop_location, deque([instruction]) + current_instruction_block)) - - raise ChannelSplit(instruction.channel_to_instruction_block.keys()) - - elif isinstance(instruction, MEASInstruction): - current_loop.add_measurements(instruction.measurements) - - else: - raise Exception('Encountered unhandled instruction {} on channel(s) {}'.format(instruction, channels)) - return root_loop - - def __getitem__(self, item: Union[ChannelID, Set[ChannelID], FrozenSet[ChannelID]]) -> Loop: - if not isinstance(item, (set, frozenset)): - item = frozenset((item,)) - elif isinstance(item, set): - item = frozenset(item) - - for channels, program in self._programs.items(): - if item.issubset(channels): - return program - raise KeyError(item) - - def to_waveform(program: Loop) -> Waveform: if program.is_leaf(): if program.repetition_count == 1: diff --git a/qupulse/_program/instructions.py b/qupulse/_program/instructions.py deleted file mode 100644 index 092da6e4b..000000000 --- a/qupulse/_program/instructions.py +++ /dev/null @@ -1,511 +0,0 @@ -"""This module defines the abstract hardware instruction model of qupulse. - -Classes: - - Trigger: Representation of a hardware trigger. - - Instruction: Base class for hardware instructions. - - CJMPInstruction: Conditional jump instruction. - - REPJInstruction: Repetition jump instruciton. - - EXECInstruction: Instruction to execute a waveform. - - GOTOInstruction: Unconditional jump instruction. - - STOPInstruction: Instruction which indicates the end of execution. - - AbstractInstructionBlock: A block of instructions (abstract base class) - - InstructionBlock: A mutable block of instructions to which instructions can be added - - ImmutableInstructionBlock: An immutable InstructionBlock - - InstructionSequence: A single final sequence of instructions. - - InstructionPointer: References an instruction's location in a sequence. -""" - -from abc import ABCMeta, abstractmethod -from typing import List, Any, Dict, Iterable, Optional, Sequence, Union, Set, Tuple -from weakref import WeakValueDictionary - -import numpy - -from qupulse.utils.types import ChannelID, MeasurementWindow, TimeType -from qupulse.comparable import Comparable -from qupulse._program.waveforms import Waveform - -__all__ = ["Trigger", - "InstructionPointer", "Instruction", "CJMPInstruction", "EXECInstruction", - "GOTOInstruction", "STOPInstruction", "REPJInstruction", "AbstractInstructionBlock", "InstructionBlock", - "ImmutableInstructionBlock", "InstructionSequence", "ChannelID"] - - -class Trigger(Comparable): - """Abstract representation of a hardware trigger for hardware based branching decisions.""" - - def __init__(self) -> None: - super().__init__() - - @property - def compare_key(self) -> Any: - return id(self) - - def __str__(self) -> str: - return "Trigger {}".format(hash(self)) - - -class InstructionPointer(Comparable): - """Reference to the location of an instruction used in expressing targets of jumps. - - The target instruction is referenced by the instruction block it resides in and its offset - within this block. - """ - - def __init__(self, block: 'AbstractInstructionBlock', offset: int=0) -> None: - """Create a new InstructionPointer instance. - - Args: - block: The instruction block the referenced instruction - resides in. - offset: The position/offset of the referenced instruction in its block. - Raises: - ValueError: If offset is negative - """ - super().__init__() - if offset < 0: - raise ValueError("offset must be a non-negative integer (was {})".format(offset)) - self.__block = block - self.__offset = offset - - @property - def block(self) -> 'AbstractInstructionBlock': - """The instruction block containing the referenced instruction.""" - return self.__block - - @property - def offset(self) -> int: - """The offset of the referenced instruction in its containing block.""" - return self.__offset - - @property - def compare_key(self) -> Any: - return id(self.__block), self.__offset - - def __str__(self) -> str: - return "IP:{0}#{1}".format(self.__block, self.__offset) - - -class Instruction(Comparable, metaclass=ABCMeta): - """A hardware instruction.""" - - def __init__(self) -> None: - super().__init__() - - -class CJMPInstruction(Instruction): - """A conditional jump hardware instruction. - - Will cause the execution to jump to the instruction indicated by the InstructionPointer held - by this CJMPInstruction if the given Trigger was fired. If not, this Instruction will have no - effect, the execution will continue with the following. - """ - - def __init__(self, trigger: Trigger, target: InstructionPointer) -> None: - """Create a new CJMPInstruction object. - - Args: - trigger (Trigger): Representation of the hardware trigger which controls whether the - conditional jump occurs or not. - target (InstructionPointer): Instruction pointer referencing the instruction targeted - by the conditional jump. - """ - super().__init__() - self.trigger = trigger - self.target = target - - @property - def compare_key(self) -> Any: - return self.trigger, self.target - - def __str__(self) -> str: - return "cjmp to {} on {}".format(self.target, self.trigger) - - -class MEASInstruction(Instruction): - """A measurement instruction. - - Cause a measurement to be executed. The instruction itself takes no time.""" - def __init__(self, measurements: List[MeasurementWindow]): - super().__init__() - - self.measurements = measurements - - @property - def compare_key(self) -> List[MeasurementWindow]: - return self.measurements - - def __str__(self): - return "meas [" + " ,".join(set(name for name, *_ in self.measurements)) + ']' - - -class REPJInstruction(Instruction): - """A repetition jump instruction. - - Will cause the execution to jump to the instruction indicated by the InstructionPointer held by - this REPJInstruction for the first n times this REPJInstruction is encountered, where n is - a parameter.""" - - def __init__(self, count: int, target: InstructionPointer) -> None: - """Create a new REPJInstruction object. - - Args: - count (int): A positive integer indicating how often the repetition jump is triggered. - target (InstructionPointer): Instruction pointer referencing the instruction targeted - by the repetition jump. - Raises: - ValueError, if count is a negative number. - """ - super().__init__() - if count < 0: - raise ValueError("Repetition count must not be negative.") - self.count = count - self.target = target - - @property - def compare_key(self) -> Any: - return self.count, self.target - - def __str__(self) -> str: - return "repj {} times to {}".format(self.count, self.target) - - -class GOTOInstruction(Instruction): - """An unconditional jump hardware instruction. - - Will cause the execution to jump to the instruction indicated by the InstructionPointer - held by this GOTOInstruction. - """ - - def __init__(self, target: InstructionPointer) -> None: - """Create a new GOTOInstruction object. - - Args: - target (InstructionPointer): Instruction pointer referencing the instruction targeted - by the unconditional jump. - """ - super().__init__() - self.target = target - - @property - def compare_key(self) -> Any: - return self.target - - def __str__(self) -> str: - return "goto to {}".format(self.target) - - -class EXECInstruction(Instruction): - """An instruction to execute/play back a waveform.""" - - def __init__(self, waveform: Waveform) -> None: - """Create a new EXECInstruction object. - - Args: - waveform: The waveform that will be executed by this instruction. - """ - super().__init__() - self.waveform = waveform - - @property - def compare_key(self) -> Waveform: - return self.waveform - - def __str__(self) -> str: - return "exec {}".format(self.waveform) - - -class STOPInstruction(Instruction): - """An instruction which indicates the end of the program.""" - - def __init__(self) -> None: - """Create a new STOPInstruction object.""" - super().__init__() - - @property - def compare_key(self) -> Any: - return 0 - - def __str__(self) -> str: - return "stop" - - -class CHANInstruction(Instruction): - """Split the control flow for different channels. - - There is no guarantee at this point that the instruction blocks have the same length. This is basically a - switch statement. - """ - - def __init__(self, channel_to_instruction_block: Dict[ChannelID, InstructionPointer]): - self.channel_to_instruction_block = channel_to_instruction_block - - @property - def compare_key(self) -> Dict[ChannelID, InstructionPointer]: - return self.channel_to_instruction_block - - def __str__(self) -> str: - return "chan " + ", ".join("{target} for {channel}" - .format(target=v, channel=k) - for k, v in sorted(self.channel_to_instruction_block.items(), key=lambda arg: arg[0])) - - def __getitem__(self, item) -> InstructionPointer: - return self.channel_to_instruction_block[item] - - -InstructionSequence = List[Instruction] # pylint: disable=invalid-name,invalid-sequence-index - - -class AbstractInstructionBlock(Comparable, metaclass=ABCMeta): - """"Abstract base class of a block of instructions representing a (sub)sequence in the control - flow of a pulse template instantiation. - - Because of included jump instructions, instruction blocks typically form a "calling" hierarchy. - Due to how the sequencing process works, this hierarchy will typically resemble the pulse - template from which it was translated closely. - - An instruction block might define a return instruction pointer specifying to which instruction - the control flow should return after execution of the block has finished. - - Instruction blocks define the item access and the iterable interface to allow access to the - contained instructions. When using these interfaces, a final stop or goto instruction is - automatically added after the regular instructions according to whether a return instruction - pointer was set or not (to return control flow to a calling block or stop the execution). - Consequently, the len() operation includes this additional instruction in the returned length. - - The property "instructions" allows access to the contained instructions without the - additional stop/goto instruction mentioned above. - - See Also: - InstructionBlock - ImmutableInstructionBlock - """ - - def __init__(self) -> None: - """Create a new AbstractInstructionBlock instance.""" - super().__init__() - - @property - @abstractmethod - def instructions(self) -> Sequence[Instruction]: - """The instructions contained in this block (excluding a final stop or return goto).""" - - @property - @abstractmethod - def return_ip(self) -> Optional[InstructionPointer]: - """The return instruction pointer indicating the instruction to which the control flow - shall return after exection of this instruction block has finished.""" - - @property - def compare_key(self) -> Any: - return id(self) - - def __str__(self) -> str: - return str(hash(self)) - - def __iter__(self) -> Iterable[Instruction]: - for instruction in self.instructions: - yield instruction - if self.return_ip is None: - yield STOPInstruction() - else: - yield GOTOInstruction(self.return_ip) - - def __getitem__(self, index: Union[int,slice]) -> Union[Instruction,Iterable[Instruction]]: - if isinstance(index, slice): - return (self[i] for i in range(*index.indices(len(self)))) - - if index > len(self.instructions) or index < -(len(self.instructions) + 1): - raise IndexError() - elif index < 0: - return self[len(self) + index] - elif index < len(self.instructions): - return self.instructions[index] - else: - if self.return_ip is None: - return STOPInstruction() - else: - return GOTOInstruction(self.return_ip) - - def __len__(self) -> int: - return len(self.instructions) + 1 - - -class InstructionBlock(AbstractInstructionBlock): - """A block of instructions representing a (sub)sequence in the control - flow of a pulse template instantiation. - - Because of included jump instructions, instruction blocks typically form a "calling" hierarchy. - Due to how the sequencing process works, this hierarchy will typically resemble the pulse - template from which it was translated closely. - - An instruction block might define a return instruction pointer specifying to which instruction - the control flow should return after execution of the block has finished. - - Instruction blocks define the item access and the iterable interface to allow access to the - contained instructions. When using these interfaces, a final stop or goto instruction is - automatically added after the regular instructions according to whether a return instruction - pointer was set or not (to return control flow to a calling block or stop the execution). - Consequently, the len() operation includes this additional instruction in the returned length. - - The property "instructions" allows access to the contained instructions without the - additional stop/goto instruction mentioned above.""" - - def __init__(self) -> None: - """Create a new InstructionBlock instance.""" - super().__init__() - self.__instruction_list = [] # type: InstructionSequence - - self.__return_ip = None - - def add_instruction(self, instruction: Instruction) -> None: - """Append an instruction at the end of this instruction block. - - Args: - instruction (Instruction): The instruction to append. - """ - self.__instruction_list.append(instruction) - - def add_instruction_exec(self, waveform: Waveform) -> None: - """Create and append a new EXECInstruction object for the given waveform at the end of this - instruction block. - - Args: - waveform (Waveform): The Waveform object referenced by the new EXECInstruction. - """ - self.add_instruction(EXECInstruction(waveform)) - - def add_instruction_goto(self, target_block: 'InstructionBlock') -> None: - """Create and append a new GOTOInstruction object with a given target block at the end of - this instruction block. - - Args: - target_block (InstructionBlock): The instruction block the new GOTOInstruction will - jump to. Execution will begin at the start of that block, i.e., the offset of the - instruction pointer of the GOTOInstruction will be zero. - """ - self.add_instruction(GOTOInstruction(InstructionPointer(target_block))) - - def add_instruction_cjmp(self, - trigger: Trigger, - target_block: 'InstructionBlock') -> None: - """Create and append a new CJMPInstruction object at the end of this instruction block. - - Args: - trigger (Trigger): The hardware trigger that will control the new CJMPInstruction. - target_block (InstructionBlock): The instruction block the new CJMPInstruction will - jump to. Execution will begin at the start of that block, i.e., the offset of the - instruction pointer of the CJMPInstruction will be zero. - """ - self.add_instruction(CJMPInstruction(trigger, InstructionPointer(target_block))) - - def add_instruction_repj(self, - count: int, - target_block: 'InstructionBlock') -> None: - """Create and append a new REPJInstruction object at the end of this instruction block. - - Args: - count (int): The amount of repetitions of the new REPJInstruction. - target_block (InstructionBlock): The instruction block the new REPJInstruction will - jump to. Execution will begin at the start of that block, i.e., the offset of the - instruction pointer of the REPJInstruction will be zero. - """ - self.add_instruction(REPJInstruction(count, InstructionPointer(target_block))) - - def add_instruction_stop(self) -> None: - """Create and append a new STOPInstruction object at the end of this instruction block.""" - self.add_instruction(STOPInstruction()) - - def add_instruction_chan(self, channel_to_instruction: Dict[ChannelID, 'InstructionBlock']) -> None: - """Create and append a new CHANInstruction at the end of this instruction block.""" - self.add_instruction(CHANInstruction({ch: InstructionPointer(block) for ch, block in channel_to_instruction.items()})) - - def add_instruction_meas(self, measurements: List['MeasurementWindow']): - """Create and append a MEASInstruction at the end of the instruction block. - - :param measurements: The measurement windows this instruction causes - """ - self.add_instruction(MEASInstruction(measurements=measurements)) - - @property - def instructions(self) -> InstructionSequence: - return self.__instruction_list.copy() - - @property - def return_ip(self) -> InstructionPointer: - return self.__return_ip - - @return_ip.setter - def return_ip(self, value: InstructionPointer) -> None: - self.__return_ip = value - - def __len__(self) -> int: - return len(self.__instruction_list) + 1 - - -class ImmutableInstructionBlock(AbstractInstructionBlock): - """An immutable instruction block which cannot be altered. - - See Also: - InstructionBlock - """ - - def __init__(self, - block: AbstractInstructionBlock, - context: Dict[AbstractInstructionBlock, 'ImmutableInstructionBlock']=None) -> None: - """Create a new ImmutableInstructionBlock hierarchy from a (mutable) InstructionBlock - hierarchy. - - Will create a deep copy (including all embedded blocks) of the given instruction block. - - Args: - block (AbstractInstructionBlock): The instruction block that will be copied into an - immutable one. - context (Dict(AbstractInstructionBlock -> ImmutableInstructionBlock)): A dictionary - to look up already existing conversions of instruction blocks. Required to resolve - return instruction pointers. Will be altered by the process. - """ - super().__init__() - if context is None: - context = dict() - self.__return_ip = None - return_ip = block.return_ip - if return_ip is not None: - self.__return_ip = InstructionPointer(context[return_ip.block], return_ip.offset) - context[block] = self - - def make_immutable(instruction: Instruction) -> Instruction: - if isinstance(instruction, GOTOInstruction): - return GOTOInstruction( - InstructionPointer( - ImmutableInstructionBlock(instruction.target.block, context), - instruction.target.offset) - ) - elif isinstance(instruction, REPJInstruction): - return REPJInstruction( - instruction.count, - InstructionPointer( - ImmutableInstructionBlock(instruction.target.block, context), - instruction.target.offset) - ) - elif isinstance(instruction, CJMPInstruction): - return CJMPInstruction( - instruction.trigger, - InstructionPointer( - ImmutableInstructionBlock(instruction.target.block, context), - instruction.target.offset) - ) - else: - return instruction - - self._instruction_tuple = tuple(make_immutable(instr) for instr in block.instructions) - - @property - def instructions(self) -> Tuple[Instruction, ...]: - return self._instruction_tuple - - @property - def return_ip(self) -> InstructionPointer: - return self.__return_ip - - diff --git a/qupulse/expressions.py b/qupulse/expressions.py index 432088193..5df10068d 100644 --- a/qupulse/expressions.py +++ b/qupulse/expressions.py @@ -2,7 +2,7 @@ This module defines the class Expression to represent mathematical expression as well as corresponding exception classes. """ -from typing import Any, Dict, Union, Sequence, Callable, TypeVar, Type +from typing import Any, Dict, Union, Sequence, Callable, TypeVar, Type, Mapping from numbers import Number import warnings import functools @@ -37,7 +37,7 @@ class Expression(AnonymousSerializable, metaclass=_ExpressionMeta): def __init__(self, *args, **kwargs): self._expression_lambda = None - def _parse_evaluate_numeric_arguments(self, eval_args: Dict[str, Number]) -> Dict[str, Number]: + def _parse_evaluate_numeric_arguments(self, eval_args: Mapping[str, Number]) -> Dict[str, Number]: try: return {v: eval_args[v] for v in self.variables} except KeyError as key_error: @@ -69,6 +69,21 @@ def _parse_evaluate_numeric_result(self, else: raise NonNumericEvaluation(self, result, call_arguments) + def evaluate_in_scope(self, scope: Mapping) -> Union[Number, numpy.ndarray]: + """Evaluate the expression by taking the variables from the given scope (typically of type Scope but it can be + any mapping.) + Args: + scope: + + Returns: + + """ + parsed_kwargs = self._parse_evaluate_numeric_arguments(scope) + result, self._expression_lambda = evaluate_lambdified(self.underlying_expression, self.variables, + parsed_kwargs, lambdified=self._expression_lambda) + + return self._parse_evaluate_numeric_result(result, scope) + def evaluate_numeric(self, **kwargs) -> Union[Number, numpy.ndarray]: parsed_kwargs = self._parse_evaluate_numeric_arguments(kwargs) @@ -84,7 +99,7 @@ def __float__(self): e = self.evaluate_numeric() return float(e) - def evaluate_symbolic(self, substitutions: Dict[Any, Any]) -> 'Expression': + def evaluate_symbolic(self, substitutions: Mapping[Any, Any]) -> 'Expression': return Expression.make(recursive_substitution(sympify(self.underlying_expression), substitutions)) @property diff --git a/qupulse/hardware/awgs/base.py b/qupulse/hardware/awgs/base.py index c31f3bfdc..019a62e87 100644 --- a/qupulse/hardware/awgs/base.py +++ b/qupulse/hardware/awgs/base.py @@ -13,15 +13,13 @@ from qupulse.hardware.util import get_sample_times from qupulse.utils.types import ChannelID from qupulse._program._loop import Loop -from qupulse._program.waveforms import Waveform from qupulse.comparable import Comparable -from qupulse._program.instructions import InstructionSequence from qupulse.utils.types import TimeType __all__ = ["AWG", "Program", "ProgramOverwriteException", "OutOfWaveformMemoryException", "AWGAmplitudeOffsetHandling"] -Program = InstructionSequence +Program = Loop class AWGAmplitudeOffsetHandling: diff --git a/qupulse/hardware/setup.py b/qupulse/hardware/setup.py index 2c88ef2d7..b49c479e0 100644 --- a/qupulse/hardware/setup.py +++ b/qupulse/hardware/setup.py @@ -2,10 +2,10 @@ from collections import defaultdict import warnings +from qupulse.pulses.parameters import Parameter, ConstantParameter from qupulse.hardware.awgs.base import AWG from qupulse.hardware.dacs import DAC -from qupulse._program._loop import MultiChannelProgram, Loop -from qupulse._program.instructions import AbstractInstructionBlock +from qupulse._program._loop import Loop from qupulse.utils.types import ChannelID @@ -69,7 +69,7 @@ def __init__(self, awg: AWG, channel_on_awg: int): super().__init__(awg=awg, channel_on_awg=channel_on_awg) -RegisteredProgram = NamedTuple('RegisteredProgram', [('program', MultiChannelProgram), +RegisteredProgram = NamedTuple('RegisteredProgram', [('program', Loop), ('measurement_windows', Dict[str, Tuple[float, float]]), ('run_callback', Callable), ('awgs_to_upload_to', Set[AWG]), @@ -91,20 +91,19 @@ def __init__(self): self._registered_programs = dict() # type: Dict[str, RegisteredProgram] def register_program(self, name: str, - instruction_block: Union[AbstractInstructionBlock, Loop], + program: Loop, run_callback=lambda: None, update=False) -> None: if not callable(run_callback): raise TypeError('The provided run_callback is not callable') - mcp = MultiChannelProgram(instruction_block) - if mcp.channels - set(self._channel_map.keys()): + channels = next(program.get_depth_first_iterator()).waveform.defined_channels + if channels - set(self._channel_map.keys()): raise KeyError('The following channels are unknown to the HardwareSetup: {}'.format( - mcp.channels - set(self._channel_map.keys()))) + channels - set(self._channel_map.keys()))) temp_measurement_windows = defaultdict(list) - for program in mcp.programs.values(): - for mw_name, begins_lengths in program.get_measurement_windows().items(): - temp_measurement_windows[mw_name].append(begins_lengths) + for mw_name, begins_lengths in program.get_measurement_windows().items(): + temp_measurement_windows[mw_name].append(begins_lengths) if set(temp_measurement_windows.keys()) - set(self._measurement_map.keys()): raise KeyError('The following measurements are not registered: {}\nUse set_measurement for that.'.format( @@ -127,41 +126,40 @@ def register_program(self, name: str, affected_dacs[dac][mask_name] = begins_lengths handled_awgs = set() - for channels, program in mcp.programs.items(): - awgs_to_channel_info = dict() + awgs_to_channel_info = dict() - def get_default_info(awg): - return ([None] * awg.num_channels, - [None] * awg.num_channels, - [None] * awg.num_markers) + def get_default_info(awg): + return ([None] * awg.num_channels, + [None] * awg.num_channels, + [None] * awg.num_markers) - for channel_id in channels: - for single_channel in self._channel_map[channel_id]: - playback_ids, voltage_trafos, marker_ids = \ + for channel_id in channels: + for single_channel in self._channel_map[channel_id]: + playback_ids, voltage_trafos, marker_ids = \ awgs_to_channel_info.setdefault(single_channel.awg, get_default_info(single_channel.awg)) - if isinstance(single_channel, PlaybackChannel): - playback_ids[single_channel.channel_on_awg] = channel_id - voltage_trafos[single_channel.channel_on_awg] = single_channel.voltage_transformation - elif isinstance(single_channel, MarkerChannel): - marker_ids[single_channel.channel_on_awg] = channel_id - - for awg, (playback_ids, voltage_trafos, marker_ids) in awgs_to_channel_info.items(): - if awg in handled_awgs: - raise ValueError('AWG has two programs') - else: - handled_awgs.add(awg) - awg.upload(name, - program=program, - channels=tuple(playback_ids), - markers=tuple(marker_ids), - force=update, - voltage_transformation=tuple(voltage_trafos)) + if isinstance(single_channel, PlaybackChannel): + playback_ids[single_channel.channel_on_awg] = channel_id + voltage_trafos[single_channel.channel_on_awg] = single_channel.voltage_transformation + elif isinstance(single_channel, MarkerChannel): + marker_ids[single_channel.channel_on_awg] = channel_id + + for awg, (playback_ids, voltage_trafos, marker_ids) in awgs_to_channel_info.items(): + if awg in handled_awgs: + raise ValueError('AWG has two programs') + else: + handled_awgs.add(awg) + awg.upload(name, + program=program, + channels=tuple(playback_ids), + markers=tuple(marker_ids), + force=update, + voltage_transformation=tuple(voltage_trafos)) for dac, dac_windows in affected_dacs.items(): dac.register_measurement_windows(name, dac_windows) - self._registered_programs[name] = RegisteredProgram(program=mcp, + self._registered_programs[name] = RegisteredProgram(program=program, measurement_windows=measurement_windows, run_callback=run_callback, awgs_to_upload_to=handled_awgs, @@ -284,6 +282,17 @@ def rm_channel(self, identifier: ChannelID) -> None: def registered_channels(self) -> Dict[ChannelID, Set[_SingleChannel]]: return self._channel_map + def update_parameters(self, name: str, parameters): + *_, awgs, dacs = self._registered_programs[name] + + for parameter_name, value in parameters.items(): + if not isinstance(value, Parameter): + parameters[parameter_name] = ConstantParameter(value) + + for awg in self.known_awgs: + if awg in awgs: + awg.set_volatile_parameters(name, parameters) + @property def registered_programs(self) -> Dict[str, RegisteredProgram]: return self._registered_programs diff --git a/qupulse/parameter_scope.py b/qupulse/parameter_scope.py new file mode 100644 index 000000000..0eeffc5e3 --- /dev/null +++ b/qupulse/parameter_scope.py @@ -0,0 +1,123 @@ +from abc import abstractmethod +from typing import Optional, Union, Dict, Any, Iterable, Set, List, Mapping, AbstractSet +from numbers import Number +import functools +import collections +import itertools +import warnings + +import sympy +import numpy + +from qupulse.serialization import AnonymousSerializable +from qupulse.expressions import Expression, ExpressionVariableMissingException +from qupulse.utils.types import HashableNumpyArray, DocStringABCMeta, Collection, SingletonABCMeta, FrozenDict + + +class Scope(Mapping[str, Number]): + __slots__ = () + + @abstractmethod + def __hash__(self): + pass + + @abstractmethod + def __eq__(self, other): + pass + + def __getitem__(self, item): + return self.get_parameter(item) + + @abstractmethod + def get_parameter(self, parameter_name: str) -> Number: + pass + + @abstractmethod + def change_constants(self, new_constants: Mapping[str, Number]) -> 'Scope': + """Change values of constants""" + + +class MappedScope(Scope): + __slots__ = ('_scope', '_mapping', 'get_parameter') + + def __init__(self, scope: Scope, mapping: FrozenDict[str, Expression]): + self._scope = scope + self._mapping = mapping + self.get_parameter = functools.lru_cache(maxsize=None)(self.get_parameter) + + def keys(self) -> AbstractSet[str]: + return self._scope.keys() | self._mapping.keys() + + def __contains__(self, item): + return item in self._mapping or item in self._scope + + def __iter__(self): + return iter(self.keys()) + + def __len__(self): + return len(self.keys()) + + def __repr__(self): + return '%s(scope=%r, mapping=%r)' % (self.__class__.__name__, self._scope, self._mapping) + + def get_parameter(self, parameter_name: str) -> Number: + expression = self._mapping.get(parameter_name, None) + scope_get_parameter = self._scope.get_parameter + if expression is None: + return scope_get_parameter(parameter_name) + else: + return expression.evaluate_in_scope(self._scope) + + def __hash__(self): + return hash((self._scope, self._mapping)) + + def __eq__(self, other: 'MappedScope'): + return self._scope == other._scope and self._mapping == other._mapping + + def change_constants(self, new_constants: Mapping[str, Number]) -> 'Scope': + scope = self._scope.change_constants(new_constants) + if scope is self._scope: + return self + else: + return MappedScope( + scope=scope, + mapping=self._mapping + ) + + +class DictScope(Scope): + __slots__ = ('_values', 'keys') + + def __init__(self, values: FrozenDict[str, Number]): + self._values = values + self.keys = self._values.keys() + + def __contains__(self, parameter_name): + return parameter_name in self._values + + def __iter__(self): + return iter(self._values) + + def __len__(self): + return len(self._values) + + def __repr__(self): + return '%s(values=%r)' % (self.__class__.__name__, self._values) + + def get_parameter(self, parameter_name) -> Number: + return self._values[parameter_name] + + def __hash__(self): + return hash(self._values) + + def __eq__(self, other: 'DictScope'): + return self._values == other._values + + def change_constants(self, new_constants: Mapping[str, Number]) -> 'Scope': + if new_constants.keys() & self._values.keys(): + return DictScope( + values=FrozenDict((parameter_name, new_constants.get(parameter_name, old_value)) + for parameter_name, old_value in self._values.items()) + ) + else: + return self diff --git a/qupulse/pulses/abstract_pulse_template.py b/qupulse/pulses/abstract_pulse_template.py index 12dcbeab2..18b7dafcf 100644 --- a/qupulse/pulses/abstract_pulse_template.py +++ b/qupulse/pulses/abstract_pulse_template.py @@ -19,7 +19,6 @@ def __init__(self, identifier: str, measurement_names: Optional[Set[str]]=None, integral: Optional[Dict[ChannelID, ExpressionScalar]]=None, duration: Optional[ExpressionScalar]=None, - is_interruptable: Optional[bool]=None, registry: Optional[PulseRegistryType]=None): """This pulse template can be used as a place holder for a pulse template with a defined interface. Pulse template properties like `defined_channels` can be passed on initialization to declare those properties who make @@ -44,7 +43,6 @@ def __init__(self, identifier: str, measurement_names: Optional property integral: Optional property duration: Optional property - is_interruptable: Optional property registry: Instance is registered here if specified """ super().__init__(identifier=identifier) @@ -70,9 +68,6 @@ def __init__(self, identifier: str, if duration: self._declared_properties['duration'] = ExpressionScalar(duration) - if is_interruptable is not None: - self._declared_properties['is_interruptable'] = bool(is_interruptable) - self._linked_target = None self.serialize_linked = False @@ -138,10 +133,7 @@ def _internal_create_program(self, **kwargs): raise NotImplementedError('this should never be called as we overrode _create_program') # pragma: no cover _create_program = partialmethod(_forward_if_linked, '_create_program') - build_sequence = partialmethod(_forward_if_linked, 'build_sequence') - requires_stop = partialmethod(_forward_if_linked, 'requires_stop') - is_interruptable = property(partial(_get_property, property_name='is_interruptable')) defined_channels = property(partial(_get_property, property_name='defined_channels')) duration = property(partial(_get_property, property_name='duration')) measurement_names = property(partial(_get_property, property_name='measurement_names')) diff --git a/qupulse/pulses/arithmetic_pulse_template.py b/qupulse/pulses/arithmetic_pulse_template.py index 77122b125..daa3c081d 100644 --- a/qupulse/pulses/arithmetic_pulse_template.py +++ b/qupulse/pulses/arithmetic_pulse_template.py @@ -8,8 +8,8 @@ from qupulse.expressions import ExpressionScalar, ExpressionLike from qupulse.serialization import Serializer, PulseRegistryType +from qupulse.parameter_scope import Scope -from qupulse.pulses.conditions import Condition from qupulse.utils.types import ChannelID from qupulse.pulses.parameters import Parameter from qupulse.pulses.pulse_template import AtomicPulseTemplate, PulseTemplate @@ -133,11 +133,6 @@ def get_measurement_windows(self, measurement_mapping=measurement_mapping)) return measurements - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition']) -> bool: - raise NotImplementedError('Easy to implement but left out for now') - def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]: data = super().get_serialization_data(serializer) data['rhs'] = self.rhs @@ -243,8 +238,8 @@ def _parse_operand(operand: Union[ExpressionLike, Mapping[ChannelID, ExpressionL return operand if isinstance(operand, ExpressionScalar) else ExpressionScalar(operand) def _get_scalar_value(self, - parameters: Dict[str, Real], - channel_mapping: Dict[str, Optional[str]]) -> Dict[ChannelID, Real]: + parameters: Mapping[str, Real], + channel_mapping: Mapping[str, Optional[str]]) -> Dict[ChannelID, Real]: """Generate a dict of real values from the scalar operand. If the scalar operand is an ExpressionScalar all channels with non None values in channel_mapping get the same @@ -260,13 +255,13 @@ def _get_scalar_value(self, The evaluation of the scalar operand for all relevant channels """ if isinstance(self._scalar, ExpressionScalar): - scalar_value = self._scalar.evaluate_numeric(**parameters) + scalar_value = self._scalar.evaluate_in_scope(parameters) return {channel_mapping[channel]: scalar_value for channel in self._pulse_template.defined_channels if channel_mapping[channel]} else: - return {channel_mapping[channel]: value.evaluate_numeric(**parameters) + return {channel_mapping[channel]: value.evaluate_in_scope(parameters) for channel, value in self._scalar.items() if channel_mapping[channel]} @@ -279,8 +274,8 @@ def rhs(self): return self._rhs def _get_transformation(self, - parameters: Dict[str, Real], - channel_mapping: Dict[ChannelID, ChannelID]) -> Transformation: + parameters: Mapping[str, Real], + channel_mapping: Mapping[ChannelID, ChannelID]) -> Transformation: transformation = IdentityTransformation() scalar_value = self._get_scalar_value(parameters=parameters, @@ -314,7 +309,7 @@ def _get_transformation(self, ) def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional[Transformation], @@ -322,16 +317,13 @@ def _internal_create_program(self, *, parent_loop: 'Loop'): """The operation is applied by modifying the transformation the pulse template operand sees.""" - scalar_operand_parameters = {parameter_name: parameters[parameter_name].get_value() - for parameter_name in self._scalar_operand_parameters} - # put arithmetic into transformation - inner_transformation = self._get_transformation(parameters=scalar_operand_parameters, + inner_transformation = self._get_transformation(parameters=scope, channel_mapping=channel_mapping) transformation = inner_transformation.chain(global_transformation) - self._pulse_template._create_program(parameters=parameters, + self._pulse_template._create_program(scope=scope, measurement_mapping=measurement_mapping, channel_mapping=channel_mapping, global_transformation=transformation, @@ -372,9 +364,6 @@ def get_serialization_data(self, serializer: Optional['Serializer'] = None) -> D return data - def build_sequence(self, *args, **kwargs): - raise NotImplementedError('Compatibility to old sequencing routines not implemented for new type') - @property def defined_channels(self): return self._pulse_template.defined_channels @@ -425,16 +414,6 @@ def integral(self) -> Dict[ChannelID, ExpressionScalar]: integral[channel] = ExpressionScalar(value) return integral - @property - def is_interruptable(self) -> bool: - return self._pulse_template.is_interruptable - - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict): - return self._pulse_template.requires_stop(parameters=parameters, - conditions=conditions) - @property def measurement_names(self) -> Set[str]: return self._pulse_template.measurement_names diff --git a/qupulse/pulses/conditions.py b/qupulse/pulses/conditions.py deleted file mode 100644 index 77b6c027a..000000000 --- a/qupulse/pulses/conditions.py +++ /dev/null @@ -1,267 +0,0 @@ -"""This module defines conditions required for branching decisions in pulse execution. - -Classes: - - Condition: Base-class for conditions. - - SoftwareCondition: A software-evaluated condition. - - HardwareCondition: A hardware-evaluated condition. - - ConditionEvaluationException. - - ConditionMissingException. -""" - -from abc import ABCMeta, abstractmethod -from typing import Dict, Optional, Callable - -from qupulse.utils.types import ChannelID -from qupulse.pulses.parameters import Parameter -from qupulse.pulses.sequencing import SequencingElement, Sequencer -from qupulse._program.instructions import InstructionBlock, InstructionPointer, Trigger - -__all__ = ["Condition", "ConditionEvaluationException", "ConditionMissingException", - "SoftwareCondition", "HardwareCondition"] - - -class Condition(metaclass=ABCMeta): - """A condition on which the execution of a pulse may depend. - - Conditions are used for branching and looping of pulses and - thus relevant for BranchPulseTemplate and LoopPulseTemplate. - Implementations of Condition may rely on software variables, - measured data or be mere placeholders for hardware triggers. - """ - def __init__(self) -> None: - super().__init__() - - @abstractmethod - def requires_stop(self) -> bool: - """Query whether evaluating this Condition object requires an interruption in execution/ - sequencing, e.g. because it depends on a value obtained during executin. - - Returns: - True, if evaluation of this Condition object requires sequencing to be interrupted. - """ - - @abstractmethod - def build_sequence_loop(self, - delegator: SequencingElement, - body: SequencingElement, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition'], - measurement_mapping: Dict[str, str], - channel_mapping: Dict[ChannelID, ChannelID], - instruction_block: InstructionBlock) -> None: - """Translate a looping SequencingElement using this Condition into an instruction sequence - for the given instruction block using sequencer and the given parameter sets. - - Args: - delegator: The SequencingElement which has delegated the invocation - of its build_sequence method to this Condition object. - body: The SequencingElement representing the loops body. - sequencer: The Sequencer object coordinating the current sequencing process. - parameters: A mapping of parameter names to Parameter objects - which will be passed to the loop body. - conditions: A mapping of condition identifier to Condition - objects which will be passed to the loop body. - instruction_block: The instruction block into which instructions - resulting from the translation of this Condition object will be placed. - See Also: - SequencingElement.build_sequence() - """ - - @abstractmethod - def build_sequence_branch(self, - delegator: SequencingElement, - if_branch: SequencingElement, - else_branch: SequencingElement, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition'], - measurement_mapping: Dict[str, str], - channel_mapping: Dict['ChannelID', 'ChannelID'], - instruction_block: InstructionBlock) -> None: - """Translate a branching SequencingElement using this Condition into an instruction sequence - for the given instruction block using sequencer and the given parameter sets. - - Args: - delegator: The SequencingElement which has delegated the invocation - of its build_sequence method to this Condition object. - if_branch: The SequencingElement representing the branch executed - if the condition holds. - else_branch: The SequencingElement representing the branch executed - if the condition does not hold. - parameters: A mapping of parameter names to Parameter objects - which will be passed to the loop body. - conditions: A mapping of condition identifier to Condition - objects which will be passed to the loop body. - instruction_block: The instruction block into which instructions - resulting from the translation of this Condition object will be placed. - - See Also: - SequencingElement.build_sequence() - """ - - -class HardwareCondition(Condition): - """A condition that will be evaluated using hardware triggers. - - The condition will be evaluate as true iff the trigger has fired before the hardware device - makes the branching decision. - - During the translation process, a HardwareCondition instance will produce code blocks for - branches/loop bodies and the corresponding conditional jump instructions. - """ - - def __init__(self, trigger: Trigger) -> None: - """Create a new HardwareCondition instance. - - Args: - trigger: The trigger handle of the corresponding hardware device.""" - super().__init__() - self.__trigger = trigger # type: Trigger - - def requires_stop(self) -> bool: - return False - - def build_sequence_loop(self, - delegator: SequencingElement, - body: SequencingElement, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict[ChannelID, ChannelID], - instruction_block: InstructionBlock) -> None: - body_block = InstructionBlock() - body_block.return_ip = InstructionPointer(instruction_block, - len(instruction_block.instructions)) - - instruction_block.add_instruction_cjmp(self.__trigger, body_block) - sequencer.push(body, parameters, conditions, window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=body_block) - - def build_sequence_branch(self, - delegator: SequencingElement, - if_branch: SequencingElement, - else_branch: SequencingElement, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict[ChannelID, ChannelID], - instruction_block: InstructionBlock) -> None: - if_block = InstructionBlock() - else_block = InstructionBlock() - - instruction_block.add_instruction_cjmp(self.__trigger, if_block) - sequencer.push(if_branch, parameters, conditions, window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=if_block) - - instruction_block.add_instruction_goto(else_block) - sequencer.push(else_branch, parameters, conditions, window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=else_block) - - if_block.return_ip = InstructionPointer(instruction_block, - len(instruction_block.instructions)) - else_block.return_ip = if_block.return_ip - - -class SoftwareCondition(Condition): - """A condition that will be evaluated in the software. - - SoftwareConditions are evaluated in software, allowing them to rely on sophisticated measurement - evaluation or to be used when the hardware device does not support trigger based jumping - instructions. - - On the downside, this means that a translation processes may be interrupted because a - SoftwareCondition relying on measurement data cannot be evaluated before that data is acquired. - In this case, the already translated part has to be executed, the measurement is made and in a - subsequent translation, the SoftwareCondition is evaluated and the corresponding instructions - of one branch/the loop body are generated without jumping instructions. - - This interruption of pulse execution might not be feasible in some environments. - """ - - def __init__(self, evaluation_callback: Callable[[int], Optional[bool]]) -> None: - """Create a new SoftwareCondition instance. - - Args: - evaluation_callback: A function handle which accepts an integer arguments and returns - a boolean value or None. The integer argument is the current iteration of loop - (starting at zero before the first loop execution). For branch sequencing, this - argument will always be zero. The callback's return value must be None iff - evaluation is currently not possible. - """ - super().__init__() - self.__callback = evaluation_callback # type: Callable[[int], Optional[bool]] - self.__loop_iteration = 0 - - def requires_stop(self) -> bool: - evaluation_result = self.__callback(self.__loop_iteration) - return evaluation_result is None - - def build_sequence_loop(self, - delegator: SequencingElement, - body: SequencingElement, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict[ChannelID, ChannelID], - instruction_block: InstructionBlock) -> None: - - evaluation_result = self.__callback(self.__loop_iteration) - if evaluation_result is None: - raise ConditionEvaluationException() - if evaluation_result is True: - sequencer.push(delegator, parameters, conditions, window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=instruction_block) - sequencer.push(body, parameters, conditions, window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=instruction_block) - self.__loop_iteration += 1 # next time, evaluate for next iteration - - def build_sequence_branch(self, - delegator: SequencingElement, - if_branch: SequencingElement, - else_branch: SequencingElement, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict[ChannelID, ChannelID], - instruction_block: InstructionBlock) -> None: - - evaluation_result = self.__callback(self.__loop_iteration) - if evaluation_result is None: - raise ConditionEvaluationException() - if evaluation_result is True: - sequencer.push(if_branch, parameters, conditions, window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=instruction_block) - else: - sequencer.push(else_branch, parameters, conditions, window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=instruction_block) - - -class ConditionEvaluationException(Exception): - """Indicates that a SoftwareCondition cannot be evaluated yet.""" - - def __str__(self) -> str: - return "The Condition can currently not be evaluated." - - -class ConditionMissingException(Exception): - """Indicates that a Condition object was not provided for a condition identifier.""" - - def __init__(self, condition_name: str) -> None: - super().__init__() - self.condition_name = condition_name - - def __str__(self) -> str: - return "Condition <{}> was referred to but not provided in the conditions dictionary."\ - .format(self.condition_name) diff --git a/qupulse/pulses/function_pulse_template.py b/qupulse/pulses/function_pulse_template.py index 120ae49ea..35a49f11c 100644 --- a/qupulse/pulses/function_pulse_template.py +++ b/qupulse/pulses/function_pulse_template.py @@ -15,7 +15,6 @@ from qupulse.expressions import ExpressionScalar from qupulse.serialization import Serializer, PulseRegistryType -from qupulse.pulses.conditions import Condition from qupulse.utils.types import ChannelID, TimeType, time_from_float from qupulse.pulses.parameters import Parameter, ParameterConstrainer, ParameterConstraint from qupulse.pulses.pulse_template import AtomicPulseTemplate, MeasurementDeclaration @@ -84,10 +83,6 @@ def function_parameters(self) -> Set[str]: def parameter_names(self) -> Set[str]: return self.function_parameters | self.measurement_parameters | self.constrained_parameters - @property - def is_interruptable(self) -> bool: - return False - @property def defined_channels(self) -> Set[ChannelID]: return {self.__channel} @@ -115,14 +110,6 @@ def build_waveform(self, duration=duration, channel=channel_mapping[self.__channel]) - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition']) -> bool: - return any( - parameters[name].requires_stop - for name in parameters.keys() if (name in self.parameter_names) - ) - def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]: data = super().get_serialization_data(serializer) diff --git a/qupulse/pulses/loop_pulse_template.py b/qupulse/pulses/loop_pulse_template.py index 2fb81956f..4bc5ee27b 100644 --- a/qupulse/pulses/loop_pulse_template.py +++ b/qupulse/pulses/loop_pulse_template.py @@ -2,13 +2,17 @@ another PulseTemplate based on a condition.""" -from typing import Dict, Set, Optional, Any, Union, Tuple, Generator, Sequence, cast, Callable +from typing import Dict, Set, Optional, Any, Union, Tuple, Iterator, Sequence, cast, Mapping import warnings +from numbers import Number import sympy from cached_property import cached_property from qupulse.serialization import Serializer, PulseRegistryType +from qupulse.parameter_scope import Scope, MappedScope, DictScope +from qupulse.utils.types import FrozenDict + from qupulse._program._loop import Loop from qupulse.expressions import ExpressionScalar @@ -43,10 +47,6 @@ def defined_channels(self) -> Set['ChannelID']: def measurement_names(self) -> Set[str]: return self.__body.measurement_names - @property - def is_interruptable(self): - raise NotImplementedError() # pragma: no cover - class ParametrizedRange: """Like the builtin python range but with parameters.""" @@ -86,10 +86,10 @@ def to_tuple(self) -> Tuple[Any, Any, Any]: self.stop.get_serialization_data(), self.step.get_serialization_data()) - def to_range(self, parameters: Dict[str, Any]) -> range: - return range(checked_int_cast(self.start.evaluate_numeric(**parameters)), - checked_int_cast(self.stop.evaluate_numeric(**parameters)), - checked_int_cast(self.step.evaluate_numeric(**parameters))) + def to_range(self, parameters: Mapping[str, Number]) -> range: + return range(checked_int_cast(self.start.evaluate_in_scope(parameters)), + checked_int_cast(self.stop.evaluate_in_scope(parameters)), + checked_int_cast(self.step.evaluate_in_scope(parameters))) @property def parameter_names(self) -> Set[str]: @@ -195,78 +195,40 @@ def parameter_names(self) -> Set[str]: parameter_names.remove(self._loop_index) return parameter_names | self._loop_range.parameter_names | self.constrained_parameters | self.measurement_parameters - def _body_parameter_generator(self, parameters: Dict[str, Parameter], forward=True) -> Generator: - loop_range_parameters = dict((parameter_name, parameters[parameter_name].get_value()) - for parameter_name in self._loop_range.parameter_names) - loop_range = self._loop_range.to_range(loop_range_parameters) + def _body_scope_generator(self, scope: Scope, forward=True) -> Iterator[Scope]: + loop_range = self._loop_range.to_range(scope) - parameters = dict((parameter_name, parameters[parameter_name]) - for parameter_name in self.body.parameter_names if parameter_name != self._loop_index) loop_range = loop_range if forward else reversed(loop_range) + loop_index_name = self._loop_index + for loop_index_value in loop_range: - local_parameters = parameters.copy() - local_parameters[self._loop_index] = ConstantParameter(loop_index_value) - yield local_parameters - - def build_sequence(self, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict[ChannelID, ChannelID], - instruction_block: InstructionBlock) -> None: - self.validate_parameter_constraints(parameters=parameters) - - self.insert_measurement_instruction(instruction_block=instruction_block, - parameters=parameters, - measurement_mapping=measurement_mapping) - - for local_parameters in self._body_parameter_generator(parameters, forward=False): - sequencer.push(self.body, - parameters=local_parameters, - conditions=conditions, - window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=instruction_block) - - def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + yield MappedScope(scope, FrozenDict([(loop_index_name, loop_index_value)])) + + def _internal_create_program(self, *, + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional['Transformation'], to_single_waveform: Set[Union[str, 'PulseTemplate']], parent_loop: Loop) -> None: - self.validate_parameter_constraints(parameters=parameters) - - try: - measurement_parameters = {parameter_name: parameters[parameter_name].get_value() - for parameter_name in self.measurement_parameters} - duration_parameters = {parameter_name: parameters[parameter_name].get_value() - for parameter_name in self.duration.variables} - except KeyError as e: - raise ParameterNotProvidedException(str(e)) from e - - if self.duration.evaluate_numeric(**duration_parameters) > 0: - measurements = self.get_measurement_windows(measurement_parameters, measurement_mapping) + self.validate_scope(scope=scope) + + if self.duration.evaluate_in_scope(scope) > 0: + measurements = self.get_measurement_windows(scope, measurement_mapping) if measurements: parent_loop.add_measurements(measurements) - for local_parameters in self._body_parameter_generator(parameters, forward=True): - self.body._create_program(parameters=local_parameters, + for local_scope in self._body_scope_generator(scope, forward=True): + self.body._create_program(scope=local_scope, measurement_mapping=measurement_mapping, channel_mapping=channel_mapping, global_transformation=global_transformation, to_single_waveform=to_single_waveform, parent_loop=parent_loop) - def build_waveform(self, parameters: Dict[str, Parameter]) -> ForLoopWaveform: - return ForLoopWaveform([self.body.build_waveform(local_parameters) - for local_parameters in self._body_parameter_generator(parameters, forward=True)]) - - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition']) -> bool: - return any(parameters[parameter_name].requires_stop for parameter_name in self._loop_range.parameter_names) + def build_waveform(self, parameter_scope: Scope) -> ForLoopWaveform: + return ForLoopWaveform([self.body.build_waveform(local_scope) + for local_scope in self._body_scope_generator(parameter_scope, forward=True)]) def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]: data = super().get_serialization_data(serializer) @@ -320,105 +282,6 @@ def integral(self) -> Dict[ChannelID, ExpressionScalar]: return body_integrals -class WhileLoopPulseTemplate(LoopPulseTemplate): - """Conditional looping in a pulse. - - A LoopPulseTemplate is a PulseTemplate whose body is repeated - during execution as long as a certain condition holds. - """ - - def __init__(self, condition: str, - body: PulseTemplate, - identifier: Optional[str]=None, - registry: PulseRegistryType=None) -> None: - """Create a new LoopPulseTemplate instance. - - Args: - condition (str): A unique identifier for the looping condition. Will be used to obtain - the Condition object from the mapping passed in during the sequencing process. - body (PulseTemplate): The PulseTemplate which will be repeated as long as the condition - holds. - identifier (str): A unique identifier for use in serialization. (optional) - """ - super().__init__(body=body, identifier=identifier) - self._condition = condition - self._register(registry=registry) - - def __str__(self) -> str: - return "LoopPulseTemplate: Condition <{}>, Body <{}>".format(self._condition, self.body) - - @property - def condition(self) -> str: - """This LoopPulseTemplate's condition.""" - return self._condition - - @property - def parameter_names(self) -> Set[str]: - return self.body.parameter_names - - @property - def duration(self) -> ExpressionScalar: - return ExpressionScalar('nan') - - def __obtain_condition_object(self, conditions: Dict[str, Condition]) -> Condition: - try: - return conditions[self._condition] - except: - raise ConditionMissingException(self._condition) - - def build_sequence(self, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict[ChannelID, ChannelID], - instruction_block: InstructionBlock) -> None: - self.__obtain_condition_object(conditions).build_sequence_loop(self, - self.body, - sequencer, - parameters, - conditions, - measurement_mapping, - channel_mapping, - instruction_block) - - def _internal_create_program(self, *, # pragma: no cover - parameters: Dict[str, Parameter], - measurement_mapping: Dict[str, Optional[str]], - channel_mapping: Dict[ChannelID, Optional[ChannelID]], - parent_loop: Loop) -> None: - raise NotImplementedError("create_program() does not handle conditions/triggers right now and cannot " - "be meaningfully implemented for a WhileLoopPulseTemplate") - - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition]) -> bool: - return self.__obtain_condition_object(conditions).requires_stop() - - def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]: - data = super().get_serialization_data(serializer) - data['body'] = self.body - - if serializer: # compatibility to old serialization routines, deprecated - data = dict() - data['body'] = serializer.dictify(self.body) - - data['condition'] = self._condition - - return data - - @classmethod - def deserialize(cls, serializer: Optional[Serializer]=None, **kwargs) -> 'WhileLoopPulseTemplate': - if serializer: # compatibility to old serialization routines, deprecated - kwargs['body'] = serializer.deserialize(kwargs['body']) - - return super().deserialize(**kwargs) - - @property - def integral(self) -> Dict[ChannelID, ExpressionScalar]: - return {c: ExpressionScalar('nan') for c in self.body.defined_channels} - - class LoopIndexNotUsedException(Exception): def __init__(self, loop_index: str, body_parameter_names: Set[str]): self.loop_index = loop_index diff --git a/qupulse/pulses/mapping_pulse_template.py b/qupulse/pulses/mapping_pulse_template.py index 4e28061aa..bbbd7e204 100644 --- a/qupulse/pulses/mapping_pulse_template.py +++ b/qupulse/pulses/mapping_pulse_template.py @@ -4,15 +4,13 @@ import numbers import collections -from qupulse.utils.types import ChannelID +from qupulse.utils.types import ChannelID, FrozenDict from qupulse.expressions import Expression, ExpressionScalar +from qupulse.parameter_scope import Scope, MappedScope from qupulse.pulses.pulse_template import PulseTemplate, MappingTuple from qupulse.pulses.parameters import Parameter, MappedParameter, ParameterNotProvidedException, ParameterConstrainer -from qupulse.pulses.sequencing import Sequencer -from qupulse._program.instructions import InstructionBlock from qupulse._program.waveforms import Waveform from qupulse._program._loop import Loop -from qupulse.pulses.conditions import Condition from qupulse.serialization import Serializer, PulseRegistryType __all__ = [ @@ -116,7 +114,7 @@ def __init__(self, template: PulseTemplate, *, template = template.template self.__template = template - self.__parameter_mapping = parameter_mapping + self.__parameter_mapping = FrozenDict(parameter_mapping) self.__external_parameters = set(itertools.chain(*(expr.variables for expr in self.__parameter_mapping.values()))) self.__external_parameters |= self.constrained_parameters self.__measurement_mapping = measurement_mapping @@ -183,7 +181,7 @@ def measurement_mapping(self) -> Dict[str, str]: return self.__measurement_mapping @property - def parameter_mapping(self) -> Dict[str, Expression]: + def parameter_mapping(self) -> FrozenDict[str, Expression]: return self.__parameter_mapping @property @@ -198,10 +196,6 @@ def parameter_names(self) -> Set[str]: def measurement_names(self) -> Set[str]: return set(self.__measurement_mapping.values()) - @property - def is_interruptable(self) -> bool: - return self.template.is_interruptable # pragma: no cover - @property def defined_channels(self) -> Set[ChannelID]: return {self.__channel_mapping[k] for k in self.template.defined_channels} - {None} @@ -275,6 +269,9 @@ def map_parameter_objects(self, parameters: Dict[str, Parameter]) -> Dict[str, {name: parameters[name] for name in mapping_function.variables}) for (parameter, mapping_function) in self.__parameter_mapping.items()} + def map_scope(self, scope: Scope) -> MappedScope: + return MappedScope(scope=scope, mapping=self.__parameter_mapping) + def map_parameters(self, parameters: Dict[str, Union[Parameter, numbers.Real]]) -> Dict[str, Union[Parameter, numbers.Real]]: @@ -308,29 +305,15 @@ def get_updated_channel_mapping(self, channel_mapping: Dict[ChannelID, return {inner_ch: None if outer_ch is None else channel_mapping[outer_ch] for inner_ch, outer_ch in self.__channel_mapping.items()} - def build_sequence(self, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict[ChannelID, ChannelID], - instruction_block: InstructionBlock) -> None: - self.template.build_sequence(sequencer, - parameters=self.map_parameter_objects(parameters), - conditions=conditions, - measurement_mapping=self.get_updated_measurement_mapping(measurement_mapping), - channel_mapping=self.get_updated_channel_mapping(channel_mapping), - instruction_block=instruction_block) - def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional['Transformation'], to_single_waveform: Set[Union[str, 'PulseTemplate']], parent_loop: Loop) -> None: # parameters are validated in map_parameters() call, no need to do it here again explicitly - self.template._create_program(parameters=self.map_parameter_objects(parameters), + self.template._create_program(scope=self.map_scope(scope), measurement_mapping=self.get_updated_measurement_mapping(measurement_mapping), channel_mapping=self.get_updated_channel_mapping(channel_mapping), global_transformation=global_transformation, @@ -353,14 +336,6 @@ def get_measurement_windows(self, measurement_mapping=self.get_updated_measurement_mapping(measurement_mapping=measurement_mapping) ) - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition]) -> bool: - return self.template.requires_stop( - self.map_parameter_objects(parameters), - conditions - ) - @property def integral(self) -> Dict[ChannelID, ExpressionScalar]: internal_integral = self.__template.integral diff --git a/qupulse/pulses/measurement.py b/qupulse/pulses/measurement.py index 45ac9cbaf..7c73d7d0f 100644 --- a/qupulse/pulses/measurement.py +++ b/qupulse/pulses/measurement.py @@ -1,4 +1,4 @@ -from typing import Optional, List, Tuple, Union, Dict, Set +from typing import Optional, List, Tuple, Union, Dict, Set, Mapping from numbers import Real import itertools @@ -23,11 +23,11 @@ def __init__(self, measurements: Optional[List[MeasurementDeclaration]]): raise ValueError('Measurement window length may not be negative') def get_measurement_windows(self, - parameters: Dict[str, Real], + parameters: Mapping[str, Real], measurement_mapping: Dict[str, Optional[str]]) -> List[MeasurementWindow]: """Calculate measurement windows with the given parameter set and rename them woth the measurement mapping""" - def get_val(v): - return v.evaluate_numeric(**parameters) + def get_val(v: Expression): + return v.evaluate_in_scope(parameters) resulting_windows = [(measurement_mapping[name], get_val(begin), get_val(length)) for name, begin, length in self._measurement_windows @@ -38,16 +38,6 @@ def get_val(v): raise ValueError('Measurement window with negative begin or length: {}, {}'.format(begin, length)) return resulting_windows - def insert_measurement_instruction(self, - instruction_block, - parameters: Dict[str, Parameter], - measurement_mapping: Dict[str, Optional[str]]): - parameters = {k: parameters[k].get_value() - for k in self.measurement_parameters} - measurements = self.get_measurement_windows(parameters, measurement_mapping) - if measurements: - instruction_block.add_instruction_meas(measurements) - @property def measurement_parameters(self) -> Set[str]: return set(var diff --git a/qupulse/pulses/multi_channel_pulse_template.py b/qupulse/pulses/multi_channel_pulse_template.py index ae23344a8..ef5e718db 100644 --- a/qupulse/pulses/multi_channel_pulse_template.py +++ b/qupulse/pulses/multi_channel_pulse_template.py @@ -13,7 +13,6 @@ from qupulse.serialization import Serializer, PulseRegistryType -from qupulse.pulses.conditions import Condition from qupulse.utils import isclose from qupulse.utils.sympy import almost_equal, Sympifyable from qupulse.utils.types import ChannelID, TimeType @@ -167,11 +166,6 @@ def get_measurement_windows(self, measurement_mapping=measurement_mapping)) return measurements - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition']) -> bool: - return any(st.requires_stop(parameters, conditions) for st in self._subtemplates) - def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]: data = super().get_serialization_data(serializer) data['subtemplates'] = self.subtemplates @@ -234,7 +228,7 @@ def _get_overwritten_channels_values(self, for name, value in self.overwritten_channels.items()} def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, global_transformation: Optional[Transformation], **kwargs): real_parameters = {name: parameters[name].get_value() for name in self.transformation_parameters} @@ -277,13 +271,6 @@ def parameter_names(self): def duration(self) -> ExpressionScalar: return self.template.duration - @property - def is_interruptable(self) -> bool: - return self.template.is_interruptable - - def requires_stop(self, *args, **kwargs) -> bool: - return self.template.requires_stop(*args, **kwargs) - @property def integral(self) -> Dict[ChannelID, ExpressionScalar]: integral = self._template.integral @@ -302,9 +289,6 @@ def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[ data['overwritten_channels'] = self._overwritten_channels return data - def build_sequence(self, *args, **kwargs): - raise NotImplementedError('Build sequence(legacy) is not implemented for new type') - class ChannelMappingException(Exception): def __init__(self, obj1, obj2, intersect_set): diff --git a/qupulse/pulses/parameters.py b/qupulse/pulses/parameters.py index 06d1cb1c2..5e57d5dae 100644 --- a/qupulse/pulses/parameters.py +++ b/qupulse/pulses/parameters.py @@ -1,28 +1,30 @@ -"""This module defines parameters and parameter declaration for the usage in pulse modelling. +"""This module defines parameters and parameter declaration for usage in pulse modelling. Classes: - Parameter: A base class representing a single pulse parameter. - ConstantParameter: A single parameter with a constant value. - MappedParameter: A parameter whose value is mathematically computed from another parameter. - ParameterNotProvidedException. - - ParameterValueIllegalException. """ from abc import abstractmethod -from typing import Optional, Union, Dict, Any, Iterable, Set, List +from typing import Optional, Union, Dict, Any, Iterable, Set, List, Mapping from numbers import Real +import warnings import sympy import numpy from qupulse.serialization import AnonymousSerializable from qupulse.expressions import Expression, ExpressionVariableMissingException +from qupulse.parameter_scope import Scope from qupulse.utils.types import HashableNumpyArray, DocStringABCMeta __all__ = ["Parameter", "ConstantParameter", "ParameterNotProvidedException", "ParameterConstraintViolation", "ParameterConstraint"] + class Parameter(metaclass=DocStringABCMeta): """A parameter for pulses. @@ -64,6 +66,8 @@ def __init__(self, value: Union[Real, numpy.ndarray, Expression, str, sympy.Expr Args: value (Real): The value of the parameter """ + warnings.warn("ConstantParameter is deprecated. Use plain number types instead", DeprecationWarning) + super().__init__() try: if isinstance(value, Real): @@ -112,6 +116,8 @@ def __init__(self, dependencies (Dict(str -> Parameter)): Parameter objects of the dependencies. May also be defined via the dependencies public property. (Optional) """ + warnings.warn("MappedParameter is deprecated. There should be no interface depending on it", DeprecationWarning) + super().__init__() self._expression = expression self.dependencies = dict() if dependencies is None else dependencies @@ -172,25 +178,25 @@ def __init__(self, relation: Union[str, sympy.Expr]): def affected_parameters(self) -> Set[str]: return set(self._expression.variables) - def is_fulfilled(self, parameter: Dict[str, Any]) -> bool: + def is_fulfilled(self, parameter: Mapping[str, Any]) -> bool: if not self.affected_parameters <= set(parameter.keys()): raise ParameterNotProvidedException((self.affected_parameters-set(parameter.keys())).pop()) - return numpy.all(self._expression.evaluate_numeric(**parameter)) + return numpy.all(self._expression.evaluate_in_scope(parameter)) @property def sympified_expression(self) -> sympy.Expr: - return self._expression.sympified_expression + return self._expression.underlying_expression def __eq__(self, other: 'ParameterConstraint') -> bool: return self._expression.underlying_expression == other._expression.underlying_expression def __str__(self) -> str: - if isinstance(self._expression.sympified_expression, sympy.Eq): - return '{}=={}'.format(self._expression.sympified_expression.lhs, - self._expression.sympified_expression.rhs) + if isinstance(self._expression.underlying_expression, sympy.Eq): + return '{}=={}'.format(self._expression.underlying_expression.lhs, + self._expression.underlying_expression.rhs) else: - return str(self._expression.sympified_expression) + return str(self._expression.underlying_expression) def __repr__(self): return 'ParameterConstraint(%s)' % repr(str(self)) @@ -224,6 +230,13 @@ def validate_parameter_constraints(self, parameters: [str, Union[Parameter, Real if not constraint.is_fulfilled(constraint_parameters): raise ParameterConstraintViolation(constraint, constraint_parameters) + def validate_scope(self, scope: Mapping[str, Real]): + for constraint in self._parameter_constraints: + if not constraint.is_fulfilled(scope): + constrained_parameters = {parameter_name: scope[parameter_name] + for parameter_name in constraint.affected_parameters} + raise ParameterConstraintViolation(constraint, constrained_parameters) + @property def constrained_parameters(self) -> Set[str]: if self._parameter_constraints: diff --git a/qupulse/pulses/plotting.py b/qupulse/pulses/plotting.py index 9902c3ee8..75b67eee7 100644 --- a/qupulse/pulses/plotting.py +++ b/qupulse/pulses/plotting.py @@ -6,7 +6,7 @@ - plot: Plot a pulse using matplotlib. """ -from typing import Dict, Tuple, Any, Generator, Optional, Set, List, Union +from typing import Dict, Tuple, Any, Optional, Set, List, Union from numbers import Real import numpy as np @@ -14,99 +14,17 @@ import operator import itertools -from qupulse.utils.types import ChannelID, MeasurementWindow, has_type_interface, TimeType +from qupulse.utils.types import ChannelID, MeasurementWindow, has_type_interface from qupulse.pulses.pulse_template import PulseTemplate from qupulse.pulses.parameters import Parameter -from qupulse._program.waveforms import Waveform, SequenceWaveform -from qupulse._program.instructions import EXECInstruction, STOPInstruction, AbstractInstructionBlock, \ - REPJInstruction, MEASInstruction, GOTOInstruction, InstructionPointer +from qupulse._program.waveforms import Waveform from qupulse._program._loop import Loop, to_waveform __all__ = ["render", "plot", "PlottingNotPossibleException"] -def iter_waveforms(instruction_block: AbstractInstructionBlock, - expected_return: Optional[InstructionPointer]=None) -> Generator[Waveform, None, None]: - # todo [2018-08-30]: seems to be unused.. remove? - for i, instruction in enumerate(instruction_block): - if isinstance(instruction, EXECInstruction): - yield instruction.waveform - elif isinstance(instruction, REPJInstruction): - expected_repj_return = InstructionPointer(instruction_block, i+1) - repj_instructions = instruction.target.block.instructions[instruction.target.offset:] - for _ in range(instruction.count): - yield from iter_waveforms(repj_instructions, expected_repj_return) - elif isinstance(instruction, MEASInstruction): - continue - elif isinstance(instruction, GOTOInstruction): - if instruction.target != expected_return: - raise NotImplementedError("Instruction block contains an unexpected GOTO instruction.") - return - elif isinstance(instruction, STOPInstruction): - return - else: - raise NotImplementedError('Rendering cannot handle instructions of type {}.'.format(type(instruction))) - - -def iter_instruction_block(instruction_block: AbstractInstructionBlock, - extract_measurements: bool) -> Tuple[list, list, TimeType]: - """Iterates over the instructions contained in an InstructionBlock (thus simulating execution). - - In effect, this function simulates the execution of the control flow represented by the passed InstructionBlock - and returns all waveforms in the order they would be executed on the hardware, along with all measurements that - would be made during that execution (if the extract_measurement argument is True). The waveforms are passed back - as Waveform objects (and are not sampled at anytime during the execution of this function). - - Args: - instruction_block: The InstructionBlock to iterate over. - extract_measurements: If True, a list of all measurement simulated during block iteration will be returned. - - Returns: - A tuple (waveforms, measurements, time) where waveforms is a sequence of Waveform objects in the order they - would be executed according to the given InstructionBlock, measurements is a similar sequence of measurements - that would be made (where each measurement is represented by a tuple (name, start_time, duration)) and time is - the total execution duration of the block (i.e. the accumulated duration of all waveforms). - measurements is an empty list if extract_measurements is not True. - """ - block_stack = [(enumerate(instruction_block), None)] - waveforms = [] - measurements = [] - time = TimeType(0) - - while block_stack: - block, expected_return = block_stack.pop() - - for i, instruction in block: - if isinstance(instruction, EXECInstruction): - waveforms.append(instruction.waveform) - time += instruction.waveform.duration - elif isinstance(instruction, REPJInstruction): - expected_repj_return = InstructionPointer(instruction_block, i+1) - repj_instructions = instruction.target.block.instructions[instruction.target.offset:] - - block_stack.append((block, expected_return)) - block_stack.extend((enumerate(repj_instructions), expected_repj_return) - for _ in range(instruction.count)) - break - elif isinstance(instruction, MEASInstruction): - if extract_measurements: - measurements.extend((name, begin+time, length) - for name, begin, length in instruction.measurements) - elif isinstance(instruction, GOTOInstruction): - if instruction.target != expected_return: - raise NotImplementedError("Instruction block contains an unexpected GOTO instruction.") - break - elif isinstance(instruction, STOPInstruction): - block_stack.clear() - break - else: - raise NotImplementedError('Rendering cannot handle instructions of type {}.'.format(type(instruction))) - - return waveforms, measurements, time - - -def render(program: Union[AbstractInstructionBlock, Loop], +def render(program: Union[Loop], sample_rate: Real = 10.0, render_measurements: bool = False, time_slice: Tuple[Real, Real] = None, @@ -133,11 +51,6 @@ def render(program: Union[AbstractInstructionBlock, Loop], """ if has_type_interface(program, Loop): waveform, measurements = _render_loop(program, render_measurements=render_measurements) - elif has_type_interface(program, AbstractInstructionBlock): - warnings.warn("InstructionBlock API is deprecated", DeprecationWarning) - if time_slice is not None: - raise ValueError("Keyword argument time_slice is not supported when rendering instruction blocks") - waveform, measurements = _render_instruction_block(program, render_measurements=render_measurements) else: raise ValueError('Cannot render an object of type %r' % type(program), program) @@ -181,19 +94,6 @@ def render(program: Union[AbstractInstructionBlock, Loop], return times, voltages, measurements -def _render_instruction_block(sequence: AbstractInstructionBlock, - render_measurements=False) -> Tuple[Optional[Waveform], List[MeasurementWindow]]: - """Transform program into single waveform and measurement windows. The specific implementation of render for - InstructionBlock arguments.""" - - waveforms, measurements, total_time = iter_instruction_block(sequence, render_measurements) - if not waveforms: - return None, measurements - sequence_waveform = SequenceWaveform(waveforms) - - return sequence_waveform, measurements - - def _render_loop(loop: Loop, render_measurements: bool,) -> Tuple[Waveform, List[MeasurementWindow]]: """Transform program into single waveform and measurement windows. diff --git a/qupulse/pulses/point_pulse_template.py b/qupulse/pulses/point_pulse_template.py index 67c0cf6df..087e32d32 100644 --- a/qupulse/pulses/point_pulse_template.py +++ b/qupulse/pulses/point_pulse_template.py @@ -9,7 +9,6 @@ from qupulse.utils.sympy import Broadcast from qupulse.utils.types import ChannelID from qupulse.expressions import Expression, ExpressionScalar -from qupulse.pulses.conditions import Condition from qupulse._program.waveforms import TableWaveform, TableWaveformEntry from qupulse.pulses.parameters import Parameter, ParameterNotProvidedException, ParameterConstraint,\ ParameterConstrainer @@ -135,17 +134,6 @@ def point_parameters(self) -> Set[str]: def parameter_names(self) -> Set[str]: return self.point_parameters | self.measurement_parameters | self.constrained_parameters - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition]) -> bool: - try: - return any( - parameters[name].requires_stop - for name in self.parameter_names - ) - except KeyError as key_error: - raise ParameterNotProvidedException(str(key_error)) from key_error - @property def integral(self) -> Dict[ChannelID, ExpressionScalar]: expressions = {channel: 0 for channel in self._channels} diff --git a/qupulse/pulses/pulse_template.py b/qupulse/pulses/pulse_template.py index a89ad7bc6..7f0886f5d 100644 --- a/qupulse/pulses/pulse_template.py +++ b/qupulse/pulses/pulse_template.py @@ -10,20 +10,18 @@ from typing import Dict, Tuple, Set, Optional, Union, List, Callable, Any, Generic, TypeVar, Mapping import itertools import collections -from numbers import Real +from numbers import Real, Number -from qupulse.utils.types import ChannelID, DocStringABCMeta +from qupulse.utils.types import ChannelID, DocStringABCMeta, FrozenDict from qupulse.serialization import Serializable from qupulse.expressions import ExpressionScalar, Expression, ExpressionLike from qupulse._program._loop import Loop, to_waveform from qupulse._program.transformation import Transformation, IdentityTransformation, ChainedTransformation, chain_transformations - -from qupulse.pulses.conditions import Condition from qupulse.pulses.parameters import Parameter, ConstantParameter, ParameterNotProvidedException -from qupulse.pulses.sequencing import Sequencer, SequencingElement, InstructionBlock from qupulse._program.waveforms import Waveform, TransformingWaveform from qupulse.pulses.measurement import MeasurementDefiner, MeasurementDeclaration +from qupulse.parameter_scope import Scope, DictScope __all__ = ["PulseTemplate", "AtomicPulseTemplate", "DoubleParameterNameException", "MappingTuple"] @@ -34,7 +32,7 @@ Tuple['PulseTemplate', Dict, Dict, Dict]] -class PulseTemplate(Serializable, SequencingElement, metaclass=DocStringABCMeta): +class PulseTemplate(Serializable, metaclass=DocStringABCMeta): """A PulseTemplate represents the parametrized general structure of a pulse. A PulseTemplate described a pulse in an abstract way: It defines the structure of a pulse @@ -62,12 +60,6 @@ def parameter_names(self) -> Set[str]: def measurement_names(self) -> Set[str]: """The set of measurement identifiers in this pulse template.""" - @property - @abstractmethod - def is_interruptable(self) -> bool: - """Return true, if this PulseTemplate contains points at which it can halt if interrupted. - """ - @property @abstractmethod def duration(self) -> ExpressionScalar: @@ -101,7 +93,7 @@ def integral(self) -> Dict[ChannelID, ExpressionScalar]: """Returns an expression giving the integral over the pulse.""" def create_program(self, *, - parameters: Optional[Mapping[str, Union[Parameter, float, Expression, str, Real]]]=None, + parameters: Optional[Mapping[str, Union[Expression, str, Number, ConstantParameter]]]=None, measurement_mapping: Optional[Mapping[str, Optional[str]]]=None, channel_mapping: Optional[Mapping[ChannelID, Optional[ChannelID]]]=None, global_transformation: Optional[Transformation]=None, @@ -141,13 +133,18 @@ def create_program(self, *, if non_unique_targets: raise ValueError('The following channels are mapped to twice', non_unique_targets) - # make sure all values in the parameters dict are of type Parameter - parameters = {key: value if isinstance(value, Parameter) else ConstantParameter(value) - for key, value in parameters.items()} + # make sure all values in the parameters dict are numbers + for parameter_name, value in parameters.items(): + if isinstance(value, Parameter): + parameters[parameter_name] = value.get_value() + elif not isinstance(value, Number): + parameters[parameter_name] = Expression(value).evaluate_numeric() + + scope = DictScope(values=FrozenDict(parameters)) root_loop = Loop() # call subclass specific implementation - self._create_program(parameters=parameters, + self._create_program(scope=scope, measurement_mapping=measurement_mapping, channel_mapping=complete_channel_mapping, global_transformation=global_transformation, @@ -160,7 +157,7 @@ def create_program(self, *, @abstractmethod def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional[Transformation], @@ -181,7 +178,7 @@ def _internal_create_program(self, *, remains unchanged in this case.""" def _create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional[Transformation], @@ -192,7 +189,7 @@ def _create_program(self, *, if self.identifier in to_single_waveform or self in to_single_waveform: root = Loop() - self._internal_create_program(parameters=parameters, + self._internal_create_program(scope=scope, measurement_mapping=measurement_mapping, channel_mapping=channel_mapping, global_transformation=None, @@ -214,7 +211,7 @@ def _create_program(self, *, parent_loop.append_child(waveform=waveform) else: - self._internal_create_program(parameters=parameters, + self._internal_create_program(scope=scope, measurement_mapping=measurement_mapping, channel_mapping=channel_mapping, to_single_waveform=to_single_waveform, @@ -285,34 +282,14 @@ def __init__(self, *, PulseTemplate.__init__(self, identifier=identifier) MeasurementDefiner.__init__(self, measurements=measurements) - def is_interruptable(self) -> bool: - return False - @property def atomicity(self) -> bool: return True measurement_names = MeasurementDefiner.measurement_names - def build_sequence(self, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, Optional[str]], - channel_mapping: Dict[ChannelID, Optional[ChannelID]], - instruction_block: InstructionBlock) -> None: - parameters = {parameter_name: parameter_value.get_value() - for parameter_name, parameter_value in parameters.items() - if parameter_name in self.parameter_names} - waveform = self.build_waveform(parameters=parameters, - channel_mapping=channel_mapping) - if waveform: - measurements = self.get_measurement_windows(parameters=parameters, measurement_mapping=measurement_mapping) - instruction_block.add_instruction_meas(measurements) - instruction_block.add_instruction_exec(waveform) - def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional[Transformation], diff --git a/qupulse/pulses/repetition_pulse_template.py b/qupulse/pulses/repetition_pulse_template.py index a8994967f..f9ab328e3 100644 --- a/qupulse/pulses/repetition_pulse_template.py +++ b/qupulse/pulses/repetition_pulse_template.py @@ -15,11 +15,7 @@ from qupulse.utils import checked_int_cast from qupulse.pulses.pulse_template import PulseTemplate from qupulse.pulses.loop_pulse_template import LoopPulseTemplate -from qupulse.pulses.sequencing import Sequencer -from qupulse._program.instructions import InstructionBlock, InstructionPointer -from qupulse._program.waveforms import RepetitionWaveform from qupulse.pulses.parameters import Parameter, ParameterConstrainer, ParameterNotProvidedException -from qupulse.pulses.conditions import Condition from qupulse.pulses.measurement import MeasurementDefiner, MeasurementDeclaration @@ -102,34 +98,8 @@ def measurement_names(self) -> Set[str]: def duration(self) -> ExpressionScalar: return self.repetition_count * self.body.duration - def build_sequence(self, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, Optional[str]], - channel_mapping: Dict[ChannelID, Optional[ChannelID]], - instruction_block: InstructionBlock) -> None: - self.validate_parameter_constraints(parameters=parameters) - try: - real_parameters = {v: parameters[v].get_value() for v in self._repetition_count.variables} - except KeyError: - raise ParameterNotProvidedException(next(v for v in self.repetition_count.variables if v not in parameters)) - - self.insert_measurement_instruction(instruction_block, - parameters=parameters, - measurement_mapping=measurement_mapping) - - repetition_count = self.get_repetition_count_value(real_parameters) - if repetition_count > 0: - body_block = InstructionBlock() - body_block.return_ip = InstructionPointer(instruction_block, len(instruction_block)) - - instruction_block.add_instruction_repj(repetition_count, body_block) - sequencer.push(self.body, parameters=parameters, conditions=conditions, - window_mapping=measurement_mapping, channel_mapping=channel_mapping, target_block=body_block) - def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional['Transformation'], @@ -161,11 +131,6 @@ def _internal_create_program(self, *, parent_loop.append_child(loop=repj_loop) - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition]) -> bool: - return any(parameters[v].requires_stop for v in self.repetition_count.variables) - def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]: data = super().get_serialization_data(serializer) data['body'] = self.body diff --git a/qupulse/pulses/sequence_pulse_template.py b/qupulse/pulses/sequence_pulse_template.py index c552a3839..fac926cb2 100644 --- a/qupulse/pulses/sequence_pulse_template.py +++ b/qupulse/pulses/sequence_pulse_template.py @@ -15,8 +15,6 @@ from qupulse.utils.types import MeasurementWindow, ChannelID, TimeType from qupulse.pulses.pulse_template import PulseTemplate, AtomicPulseTemplate from qupulse.pulses.parameters import Parameter, ParameterConstrainer, ParameterNotProvidedException -from qupulse.pulses.sequencing import InstructionBlock, Sequencer -from qupulse.pulses.conditions import Condition from qupulse.pulses.mapping_pulse_template import MappingPulseTemplate, MappingTuple from qupulse._program.waveforms import SequenceWaveform from qupulse.pulses.measurement import MeasurementDeclaration, MeasurementDefiner @@ -115,10 +113,6 @@ def parameter_names(self) -> Set[str]: def subtemplates(self) -> List[MappingPulseTemplate]: return self.__subtemplates - @property - def is_interruptable(self) -> bool: - return any(st.is_interruptable for st in self.subtemplates) - @cached_property def duration(self) -> Expression: return sum(sub.duration for sub in self.__subtemplates) @@ -132,13 +126,6 @@ def measurement_names(self) -> Set[str]: return set.union(MeasurementDefiner.measurement_names.fget(self), *(st.measurement_names for st in self.subtemplates)) - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition']) -> bool: - """Returns the stop requirement of the first subtemplate. If a later subtemplate requires a stop the - SequencePulseTemplate can be partially sequenced.""" - return self.__subtemplates[0].requires_stop(parameters, conditions) if self.__subtemplates else False - def build_waveform(self, parameters: Dict[str, Real], channel_mapping: Dict[ChannelID, ChannelID]) -> SequenceWaveform: @@ -147,27 +134,8 @@ def build_waveform(self, channel_mapping=channel_mapping) for sub_template in self.__subtemplates]) - def build_sequence(self, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict['ChannelID', 'ChannelID'], - instruction_block: InstructionBlock) -> None: - self.validate_parameter_constraints(parameters=parameters) - self.insert_measurement_instruction(instruction_block=instruction_block, - parameters=parameters, - measurement_mapping=measurement_mapping) - for subtemplate in reversed(self.subtemplates): - sequencer.push(subtemplate, - parameters=parameters, - conditions=conditions, - window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=instruction_block) - def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional['Transformation'], diff --git a/qupulse/pulses/sequencing.py b/qupulse/pulses/sequencing.py deleted file mode 100644 index a0ac18b41..000000000 --- a/qupulse/pulses/sequencing.py +++ /dev/null @@ -1,219 +0,0 @@ -"""This module provides sequencing functionality: It defines classes and algorithms required -to translate PulseTemplates into a hardware understandable abstract instruction sequence of -instantiated pulses or waveforms. - -Classes: - - SequencingElement: Interface for objects that can be translated into instruction sequences. - - Sequencer: Controller of the sequencing/translation process. -""" - -from abc import ABCMeta, abstractmethod -from typing import Tuple, Dict, Union, Optional - -# solve circular dependence of type hints -from . import conditions - -from qupulse.utils.types import ChannelID -from qupulse._program.instructions import InstructionBlock, ImmutableInstructionBlock -from qupulse._program.waveforms import Waveform -from qupulse.pulses.parameters import Parameter, ConstantParameter - - -__all__ = ["SequencingElement", "Sequencer"] - - -class SequencingElement(metaclass=ABCMeta): - """An entity which can be sequenced using Sequencer. - - DEPRECATED (September 2018). This is outdated code. For obtaining pulses for execution from pulse templates, refer - to :meth:`.PulseTemplate.create_program`. - - See also: - Sequencer - """ - - def __init__(self) -> None: - pass - - def atomicity(self) -> bool: - """Is the element translated to a single EXECInstruction with one waveform""" - raise NotImplementedError() - - @abstractmethod - def build_sequence(self, - sequencer: 'Sequencer', - parameters: Dict[str, Parameter], - conditions: Dict[str, 'conditions.Condition'], - measurement_mapping: Dict[str, Optional[str]], - channel_mapping: Dict[ChannelID, Optional[ChannelID]], - instruction_block: InstructionBlock) -> None: - """Translate this SequencingElement into an instruction sequence for the given - instruction_block using sequencer and the given parameter and condition sets. - - Implementation guide: Use instruction_block methods to add instructions or create new InstructionBlocks. Use - sequencer to push child elements to the translation stack. - - Args: - sequencer: The Sequencer object coordinating the current sequencing process. - parameters: A mapping of parameter names to Parameter objects. - conditions: A mapping of condition identifiers to Condition - measurement_mapping: A mapping of measurement window names. Windows that are mapped to None are omitted. - channel_mapping: A mapping of channel names. Channels that are mapped to None are omitted. - instruction_block: The instruction block into which instructions resulting from the translation of this - SequencingElement will be placed. - """ - - @abstractmethod - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'conditions.Condition']) -> bool: - """Return True if this SequencingElement cannot be translated yet. - - Sequencer will check requires_stop() before calling build_sequence(). If requires_stop() - returns True, Sequencer interrupts the current translation process and will not call - build_sequence(). - - Implementation guide: requires_stop() should only return True, if this SequencingElement - cannot be translated, i.e., the return value should only depend on the parameters/conditions - of this SequencingElement, not on possible child elements. - If this SequencingElement contains a child element which requires a stop, it should be - pushed to the sequencing stack nonetheless. The requires_stop information of the child - will be regarded during translation of that element. - - Args: - parameters: A mapping of parameter names to Parameter objects. - conditions: A mapping of condition identifiers to Condition objects. - Returns: - True, if this SequencingElement cannot be translated yet. False, otherwise. - """ - - -class Sequencer: - """Translates tree structures of SequencingElement objects to linear instruction sequences. - - DEPRECATED (September 2018). This is outdated code. For obtaining pulses for execution from pulse templates, refer - to :meth:`.PulseTemplate.create_program`. - - The concept of the sequencing process itself is described in detail in Section 1.4 of the - documentation. Sequencer controls the process and invokes the translation functions of - SequencingElements on the sequencing stack, which in turn implement the details of the - translation of that specific SequencingElement. - - Sequencer manages a main InstructionBlock into which all instructions are translated by default. - Since additional InstructionBlocks may be created during the sequencing process due to looping - and branching, Sequencer maintains several several sequencing stacks - one for each block - - simultaneously and continues the translation until no stack holds objects that may be - translated anymore before compiling the final sequence and interrupting or finishing the - sequencing process. - - See also: - SequencingElement - """ - - StackElement = Tuple[SequencingElement, Dict[str, Parameter], Dict[str, 'conditions.Condition'], Dict[str,str]] - - def __init__(self) -> None: - """Create a Sequencer.""" - super().__init__() - self.__waveforms = dict() # type: Dict[int, Waveform] - self.__main_block = InstructionBlock() - self.__sequencing_stacks = \ - {self.__main_block: []} # type: Dict[InstructionBlock, List[Sequencer.StackElement]] - - def push(self, - sequencing_element: SequencingElement, - parameters: Optional[Dict[str, Union[Parameter, float]]]=None, - conditions: Optional[Dict[str, 'conditions.Condition']]=None, - *, - window_mapping: Optional[Dict[str, Optional[str]]]=None, - channel_mapping: Optional[Dict[ChannelID, Optional[ChannelID]]]=None, - target_block: Optional[InstructionBlock]=None) -> None: - """Add an element to the translation stack of the target_block with the given set of - parameters. - - The element will be on top of the stack, i.e., it is the first to be translated if no - subsequent calls to push with the same target_block occur. - - Args: - sequencing_element (SequencingElement): The SequencingElement to push to the stack. - parameters (Dict(str -> (Parameter or float)): A mapping of parameters names defined - in the SequencingElement to Parameter objects or constant float values. In the - latter case, the float values are encapsulated into ConstantParameter objects. - Optional, if no conditions are defined by the SequencingElement. (default: None) - conditions (Dict(str -> Condition)): A mapping of condition identifier defined by the - SequencingElement to Condition objects. Optional, if no conditions are defined by - the SequencingElement. (default: None) - window_mapping (Dict(str -> str)): Mapping of the measurement window names of the sequence element - channel_mapping (Dict(ChannelID -> ChannelID)): Mapping of the defined channels - target_block (InstructionBlock): The instruction block into which instructions resulting - from the translation of the SequencingElement will be placed. Optional. If not - provided, the main instruction block will be targeted. (default: None) - """ - if parameters is None: - parameters = dict() - if conditions is None: - conditions = dict() - if target_block is None: - target_block = self.__main_block - if window_mapping is None: - if hasattr(sequencing_element, 'measurement_names'): - window_mapping = {wn: wn for wn in sequencing_element.measurement_names} - else: - window_mapping = dict() - if channel_mapping is None: - if hasattr(sequencing_element, 'defined_channels'): - channel_mapping = {cn: cn for cn in sequencing_element.defined_channels} - else: - channel_mapping = dict() - for (key, value) in parameters.items(): - if not isinstance(value, Parameter): - parameters[key] = ConstantParameter(value) - - if target_block not in self.__sequencing_stacks: - self.__sequencing_stacks[target_block] = [] - - self.__sequencing_stacks[target_block].append((sequencing_element, parameters, conditions, window_mapping, - channel_mapping)) - - def build(self) -> ImmutableInstructionBlock: - """Start the translation process. Translate all elements currently on the translation stacks - into an InstructionBlock hierarchy. - - Processes all sequencing stacks (for each InstructionBlock) until each stack is either - empty or its topmost element requires a stop. If build is called after a previous - translation process where some elements required a stop (i.e., has_finished returned False), - it will append new instructions to the previously generated and returned blocks. - - Returns: - The instruction block (hierarchy) resulting from the translation of the (remaining) - SequencingElements on the sequencing stacks. - """ - if not self.has_finished(): - shall_continue = True # shall_continue will only be False, if the first element on all - # stacks requires a stop or all stacks are empty - while shall_continue: - shall_continue = False - for target_block, sequencing_stack in self.__sequencing_stacks.copy().items(): - while sequencing_stack: - (element, parameters, conditions, window_mapping, channel_mapping) = sequencing_stack[-1] - if not element.requires_stop(parameters, conditions): - shall_continue |= True - sequencing_stack.pop() - element.build_sequence(self, parameters, conditions, window_mapping, - channel_mapping, target_block) - else: break - - return ImmutableInstructionBlock(self.__main_block, dict()) - - def has_finished(self) -> bool: - """Check whether all translation stacks are empty. Indicates that the translation is - complete. - - Note that has_finished will return False, if there are stack elements that require a stop. - In this case, calling build will only have an effect if these elements no longer require a - stop, e.g. when required measurement results have been acquired since the last translation. - - Returns: - Returns True, if all translation stacks are empty, i.e., the translation is complete. - """ - return not any(self.__sequencing_stacks.values()) diff --git a/qupulse/pulses/table_pulse_template.py b/qupulse/pulses/table_pulse_template.py index 2307b87ed..a5d8ce4f3 100644 --- a/qupulse/pulses/table_pulse_template.py +++ b/qupulse/pulses/table_pulse_template.py @@ -25,7 +25,6 @@ from qupulse._program.waveforms import TableWaveform, TableWaveformEntry from qupulse.expressions import ExpressionScalar, Expression from qupulse.pulses.multi_channel_pulse_template import MultiChannelWaveform -from qupulse.pulses.conditions import Condition __all__ = ["TablePulseTemplate", "concatenate"] @@ -205,10 +204,6 @@ def table_parameters(self) -> Set[str]: def parameter_names(self) -> Set[str]: return self.table_parameters | self.measurement_parameters | self.constrained_parameters - @property - def is_interruptable(self) -> bool: - return False - @property def duration(self) -> ExpressionScalar: return self._duration @@ -222,17 +217,6 @@ def calculate_duration(self) -> ExpressionScalar: def defined_channels(self) -> Set[ChannelID]: return set(self._entries.keys()) - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition']) -> bool: - try: - return any( - parameters[name].requires_stop - for name in self.parameter_names - ) - except KeyError as key_error: - raise ParameterNotProvidedException(str(key_error)) from key_error - def get_serialization_data(self, serializer: Optional[Serializer]=None) -> Dict[str, Any]: data = super().get_serialization_data(serializer) diff --git a/qupulse/serialization.py b/qupulse/serialization.py index f1fef11ad..5781b2605 100644 --- a/qupulse/serialization.py +++ b/qupulse/serialization.py @@ -472,8 +472,6 @@ def __init__(self, identifier: Optional[str]=None) -> None: Raises: ValueError: If identifier is the empty string """ - super().__init__() - if identifier == '': raise ValueError("Identifier must not be empty.") self.__identifier = identifier diff --git a/qupulse/utils/types.py b/qupulse/utils/types.py index 4cbf93486..95a76698c 100644 --- a/qupulse/utils/types.py +++ b/qupulse/utils/types.py @@ -6,6 +6,7 @@ import functools import warnings import collections +import operator import numpy @@ -335,3 +336,32 @@ class Collection(typing.Sized, typing.Iterable[typing.T_co], typing.Container[ty """Fallback for typing.Collection if python 3.5 copied from https://github.com/python/cpython/blob/3.5/Lib/typing.py""" __slots__ = () + + +class FrozenDict(typing.Mapping): + def __init__(self, *args, **kwargs): + self._dict = collections.OrderedDict(*args, **kwargs) + self._hash = None + + def __getitem__(self, item): + return self._dict[item] + + def __contains__(self, item): + return item in self._dict + + def __iter__(self): + return iter(self._dict) + + def __len__(self): + return len(self._dict) + + def __repr__(self): + return '<%s %r>' % (self.__class__.__name__, self._dict) + + def __hash__(self): + if self._hash is None: + self._hash = functools.reduce(operator.xor, map(hash, self._dict.items())) + return self._hash + + def __eq__(self, other): + return other == self._dict diff --git a/tests/_program/instructions_tests.py b/tests/_program/instructions_tests.py deleted file mode 100644 index 0c666057b..000000000 --- a/tests/_program/instructions_tests.py +++ /dev/null @@ -1,690 +0,0 @@ -import unittest -import numpy -from typing import Dict, Any, List - -from qupulse._program.instructions import InstructionBlock, InstructionPointer,\ - Trigger, CJMPInstruction, REPJInstruction, GOTOInstruction, EXECInstruction, STOPInstruction,\ - InstructionSequence, AbstractInstructionBlock, ImmutableInstructionBlock, Instruction, CHANInstruction - -from tests.pulses.sequencing_dummies import DummyWaveform, DummyInstructionBlock - - -class InstructionPointerTest(unittest.TestCase): - - def test_invalid_offset(self) -> None: - block = InstructionBlock() - with self.assertRaises(ValueError): - InstructionPointer(block, -1) - with self.assertRaises(ValueError): - InstructionPointer(block, -12) - - def test_initialization_main_block(self) -> None: - block = InstructionBlock() - for offset in [0, 1, 924]: - ip = InstructionPointer(block, offset) - self.assertIs(block, ip.block) - self.assertEqual(offset, ip.offset) - - def test_initialization_relative_block(self) -> None: - block = InstructionBlock() - for offset in [0, 1, 924]: - ip = InstructionPointer(block, offset) - self.assertIs(block, ip.block) - self.assertEqual(offset, ip.offset) - - def test_equality(self) -> None: - blocks = [InstructionBlock(), InstructionBlock()] - blocks.append(InstructionBlock()) - ips = [] - for block in blocks: - for offset in [0, 1, 2352]: - ip = InstructionPointer(block, offset) - self.assertEqual(ip, ip) - for other in ips: - self.assertNotEqual(ip, other) - self.assertNotEqual(other, ip) - self.assertNotEqual(hash(ip), hash(other)) - ips.append(ip) - - -class TriggerTest(unittest.TestCase): - - def test_equality(self) -> None: - t1 = Trigger() - t2 = Trigger() - self.assertEqual(t1, t1) - self.assertNotEqual(t1, t2) - self.assertNotEqual(t2, t1) - self.assertNotEqual(hash(t1), hash(t2)) - - -class CJMPInstructionTest(unittest.TestCase): - - def test_initialization(self) -> None: - block = InstructionBlock() - trigger = Trigger() - for offset in [0, 1, 23]: - instr = CJMPInstruction(trigger, InstructionPointer(block, offset)) - self.assertEqual(trigger, instr.trigger) - self.assertEqual(block, instr.target.block) - self.assertEqual(offset, instr.target.offset) - - def test_equality(self) -> None: - blocks = [InstructionBlock(), InstructionBlock()] - for offset in [0, 1, 23]: - instrA = CJMPInstruction(0, InstructionPointer(blocks[0], offset)) - instrB = CJMPInstruction(0, InstructionPointer(blocks[0], offset)) - self.assertEqual(instrA, instrB) - self.assertEqual(instrB, instrA) - instrs = [] - for trigger in [Trigger(), Trigger()]: - for block in blocks: - for offset in [0, 17]: - instruction = CJMPInstruction(trigger, InstructionPointer(block, offset)) - self.assertEqual(instruction, instruction) - for other in instrs: - self.assertNotEqual(instruction, other) - self.assertNotEqual(other, instruction) - self.assertNotEqual(hash(instruction), hash(other)) - instrs.append(instruction) - - def test_str(self) -> None: - block = DummyInstructionBlock() - trigger = Trigger() - instr = CJMPInstruction(trigger, InstructionPointer(block, 3)) - self.assertEqual("cjmp to {} on {}".format(InstructionPointer(block, 3), trigger), str(instr)) - - -class REPJInstructionTest(unittest.TestCase): - - def test_initialization(self) -> None: - block = InstructionBlock() - for count in [0, 1, 47]: - for offset in [0, 1, 23]: - instr = REPJInstruction(count, InstructionPointer(block, offset)) - self.assertEqual(count, instr.count) - self.assertEqual(block, instr.target.block) - self.assertEqual(offset, instr.target.offset) - - def test_negative_count(self) -> None: - with self.assertRaises(ValueError): - REPJInstruction(-3, InstructionPointer(InstructionBlock)) - - def test_equality(self) -> None: - blocks = [InstructionBlock(), InstructionBlock()] - for count in [0, 1, 47]: - for offset in [0, 1, 23]: - instrA = REPJInstruction(count, InstructionPointer(blocks[0], offset)) - instrB = REPJInstruction(count, InstructionPointer(blocks[0], offset)) - self.assertEqual(instrA, instrB) - self.assertEqual(instrB, instrA) - instrs = [] - for count in [0, 1, 43]: - for block in blocks: - for offset in [0, 17]: - instruction = REPJInstruction(count, InstructionPointer(block, offset)) - self.assertEqual(instruction, instruction) - for other in instrs: - self.assertNotEqual(instruction, other) - self.assertNotEqual(other, instruction) - self.assertNotEqual(hash(instruction), hash(other)) - instrs.append(instruction) - - def test_str(self) -> None: - block = DummyInstructionBlock() - instr = REPJInstruction(7, InstructionPointer(block, 3)) - self.assertEqual("repj {} times to {}".format(7, InstructionPointer(block, 3)), str(instr)) - - -class GOTOInstructionTest(unittest.TestCase): - - def test_initialization(self) -> None: - block = InstructionBlock() - for offset in [0, 1, 23]: - instr = GOTOInstruction(InstructionPointer(block, offset)) - self.assertIs(block, instr.target.block) - self.assertEqual(offset, instr.target.offset) - - def test_equality(self) -> None: - blocks = [InstructionBlock(), InstructionBlock()] - for offset in [0, 1, 23]: - instrA = GOTOInstruction(InstructionPointer(blocks[0], offset)) - instrB = GOTOInstruction(InstructionPointer(blocks[0], offset)) - self.assertEqual(instrA, instrB) - self.assertEqual(instrB, instrA) - instrs = [] - for block in blocks: - for offset in [0, 17]: - instruction = GOTOInstruction(InstructionPointer(block, offset)) - self.assertEqual(instruction, instruction) - for other in instrs: - self.assertNotEqual(instruction, other) - self.assertNotEqual(other, instruction) - self.assertNotEqual(hash(instruction), hash(other)) - instrs.append(instruction) - - def test_str(self) -> None: - block = DummyInstructionBlock() - instr = GOTOInstruction(InstructionPointer(block, 3)) - self.assertEqual("goto to {}".format(str(InstructionPointer(block, 3))), str(instr)) - - -class CHANInstructionTest(unittest.TestCase): - def test_compare_key(self): - c_to_i = dict(a=5) - - instr = CHANInstruction(c_to_i) - - self.assertIs(instr.compare_key, c_to_i) - self.assertIs(instr.channel_to_instruction_block, c_to_i) - - def test_get_item(self): - c_to_i = dict(a='b') - instr = CHANInstruction(c_to_i) - - self.assertIs(instr['a'], c_to_i['a']) - - def test_str(self): - c_to_i = dict(a='b', c='d') - instr = CHANInstruction(c_to_i) - - self.assertEqual(str(instr), 'chan b for a, d for c') - -class EXECInstructionTest(unittest.TestCase): - - def test_initialization(self): - waveform = DummyWaveform() - instr = EXECInstruction(waveform) - self.assertIs(waveform, instr.waveform) - - def test_equality(self): - wf1 = DummyWaveform() - wf2 = DummyWaveform() - instr11 = EXECInstruction(wf1) - instr12 = EXECInstruction(wf1) - instr20 = EXECInstruction(wf2) - self.assertEqual(instr11, instr11) - self.assertEqual(instr11, instr12) - self.assertEqual(instr12, instr11) - self.assertNotEqual(instr11, instr20) - self.assertNotEqual(instr20, instr11) - self.assertEqual(hash(instr11), hash(instr12)) - self.assertNotEqual(hash(instr11), hash(instr20)) - - def test_str(self) -> None: - wf = DummyWaveform() - instr = EXECInstruction(wf) - self.assertEqual("exec {}".format(str(wf)), str(instr)) - - -class STOPInstructionTest(unittest.TestCase): - - def test_str(self): - instr = STOPInstruction() - self.assertEqual('stop', str(instr)) - - def test_equality(self): - instr1 = STOPInstruction() - instr2 = STOPInstruction() - self.assertEqual(instr1, instr1) - self.assertEqual(instr1, instr2) - self.assertEqual(instr2, instr1) - self.assertEqual(hash(instr1), hash(instr2)) - - -class AbstractInstructionBlockStub(AbstractInstructionBlock): - - def __init__(self, instructions: List[Instruction], return_ip: InstructionPointer) -> None: - super().__init__() - self.__instructions = instructions - self.__return_ip = return_ip - - @property - def instructions(self) -> List[Instruction]: - return self.__instructions - - @property - def return_ip(self) -> InstructionPointer: - return self.__return_ip - - @property - def compare_key(self) -> Any: - return id(self) - - -class AbstractInstructionBlockTest(unittest.TestCase): - - def test_len_empty(self) -> None: - block = AbstractInstructionBlockStub([], None) - self.assertEqual(1, len(block)) - self.assertEqual(0, len(block.instructions)) - - def test_len(self) -> None: - block = AbstractInstructionBlockStub([EXECInstruction(DummyWaveform())], None) - self.assertEqual(2, len(block)) - self.assertEqual(1, len(block.instructions)) - - def test_iterable_empty_no_return(self) -> None: - block = AbstractInstructionBlockStub([], None) - count = 0 - for instruction in block: - self.assertEqual(0, count) - self.assertIsInstance(instruction, STOPInstruction) - count += 1 - - def test_iterable_empty_return(self) -> None: - parent_block = InstructionBlock() - block = AbstractInstructionBlockStub([], InstructionPointer(parent_block, 13)) - count = 0 - for instruction in block: - self.assertEqual(0, count) - self.assertIsInstance(instruction, GOTOInstruction) - self.assertEqual(InstructionPointer(parent_block, 13), instruction.target) - count += 1 - - def test_iterable_no_return(self) -> None: - wf = DummyWaveform() - block = AbstractInstructionBlockStub([EXECInstruction(wf)], None) - count = 0 - for expected_instruction, instruction in zip([EXECInstruction(wf), STOPInstruction()], block): - self.assertEqual(expected_instruction, instruction) - count += 1 - self.assertEqual(2, count) - - def test_iterable_return(self) -> None: - parent_block = InstructionBlock() - wf = DummyWaveform() - block = AbstractInstructionBlockStub([EXECInstruction(wf)], InstructionPointer(parent_block, 11)) - count = 0 - for expected_instruction, instruction in zip([EXECInstruction(wf), GOTOInstruction(InstructionPointer(parent_block, 11))], block): - self.assertEqual(expected_instruction, instruction) - count += 1 - self.assertEqual(2, count); - - def test_item_access_empty_no_return(self) -> None: - block = AbstractInstructionBlockStub([], None) - self.assertEqual(STOPInstruction(), block[0]) - with self.assertRaises(IndexError): - block[1] - self.assertEqual(STOPInstruction(), block[-1]) - with self.assertRaises(IndexError): - block[-2] - - def test_item_access_empty_return(self) -> None: - parent_block = InstructionBlock() - block = AbstractInstructionBlockStub([], InstructionPointer(parent_block, 84)) - self.assertEqual(GOTOInstruction(InstructionPointer(parent_block, 84)), block[0]) - with self.assertRaises(IndexError): - block[1] - self.assertEqual(GOTOInstruction(InstructionPointer(parent_block, 84)), block[-1]) - with self.assertRaises(IndexError): - block[-2] - - def test_item_access_no_return(self) -> None: - wf = DummyWaveform() - block = AbstractInstructionBlockStub([EXECInstruction(wf)], None) - self.assertEqual(EXECInstruction(wf), block[0]) - self.assertEqual(STOPInstruction(), block[1]) - with self.assertRaises(IndexError): - block[2] - self.assertEqual(STOPInstruction(), block[-1]) - self.assertEqual(EXECInstruction(wf), block[-2]) - with self.assertRaises(IndexError): - block[-3] - - def test_item_access_return(self) -> None: - wf = DummyWaveform() - parent_block = InstructionBlock() - block = AbstractInstructionBlockStub([EXECInstruction(wf)], InstructionPointer(parent_block, 29)) - self.assertEqual(EXECInstruction(wf), block[0]) - self.assertEqual(GOTOInstruction(InstructionPointer(parent_block, 29)), block[1]) - with self.assertRaises(IndexError): - block[2] - self.assertEqual(GOTOInstruction(InstructionPointer(parent_block, 29)), block[-1]) - self.assertEqual(EXECInstruction(wf), block[-2]) - with self.assertRaises(IndexError): - block[-3] - - def test_sliced_item_access(self) -> None: - wf = DummyWaveform() - parent_block = InstructionBlock() - block = AbstractInstructionBlockStub([EXECInstruction(wf), EXECInstruction(wf)], InstructionPointer(parent_block, 29)) - for instruction in block[:-1]: - self.assertEqual(EXECInstruction(wf), instruction) - - expections = [EXECInstruction(wf), EXECInstruction(wf), GOTOInstruction(InstructionPointer(parent_block, 29))] - - for expected, instruction in zip(expections,block[:4]): - self.assertEqual(expected, instruction) - - for instruction, expected in zip(block[::-1], reversed(expections)): - self.assertEqual(expected, instruction) - - with self.assertRaises(StopIteration): - next(iter(block[3:])) - - -class InstructionBlockTest(unittest.TestCase): - - def __init__(self, method_name: str) -> None: - super().__init__(method_name) - self.maxDiff = None - - def __verify_block(self, block: InstructionBlock, - expected_instructions: InstructionSequence, - expected_compiled_instructions: InstructionSequence, - expected_return_ip: InstructionPointer) -> None: - self.assertEqual(len(expected_instructions), len(block.instructions)) - self.assertEqual(expected_instructions, block.instructions) - self.assertEqual(expected_compiled_instructions, [x for x in block]) - self.assertEqual(expected_return_ip, block.return_ip) - - def test_empty_unreturning_block(self) -> None: - block = InstructionBlock() - self.__verify_block(block, [], [STOPInstruction()], None) - - def test_empty_returning_block(self) -> None: - return_block = InstructionBlock() - block = InstructionBlock() - ip = InstructionPointer(return_block, 7) - block.return_ip = ip - self.__verify_block(block, [], [GOTOInstruction(ip)], ip) - - def test_create_embedded_block(self) -> None: - parent_block = InstructionBlock() - block = InstructionBlock() - block.return_ip = InstructionPointer(parent_block, 18) - self.__verify_block(block, [], [GOTOInstruction(InstructionPointer(parent_block, 18))], InstructionPointer(parent_block, 18)) - self.__verify_block(parent_block, [], [STOPInstruction()], None) - - def test_add_instruction_exec(self) -> None: - block = InstructionBlock() - expected_instructions = [] - - waveforms = [DummyWaveform(), DummyWaveform(), DummyWaveform()] - LOOKUP = [0, 1, 1, 0, 2, 1, 0, 0, 0, 1, 2, 2] - for id in LOOKUP: - waveform = waveforms[id] - instruction = EXECInstruction(waveform) - expected_instructions.append(instruction) - block.add_instruction_exec(waveform) - - expected_compiled_instructions = expected_instructions.copy() - expected_compiled_instructions.append(STOPInstruction()) - self.__verify_block(block, expected_instructions, expected_compiled_instructions, None) - - def test_add_instruction_goto(self) -> None: - block = InstructionBlock() - expected_instructions = [] - - targets = [InstructionBlock(), InstructionBlock(), InstructionBlock()] - LOOKUP = [0, 1, 1, 0, 2, 1, 0, 0, 0, 1, 2, 2] - for id in LOOKUP: - target = targets[id] - instruction = GOTOInstruction(InstructionPointer(target)) - expected_instructions.append(instruction) - block.add_instruction_goto(target) - - expected_compiled_instructions = expected_instructions.copy() - expected_compiled_instructions.append(STOPInstruction()) - self.__verify_block(block, expected_instructions, expected_compiled_instructions, None) - - def test_add_instruction_cjmp(self) -> None: - block = InstructionBlock() - expected_instructions = [] - - targets = [InstructionBlock(), InstructionBlock(), InstructionBlock()] - triggers = [Trigger(), Trigger()] - LOOKUP = [(0, 0), (1, 0), (1, 1), (0, 1), (2, 0), (1, 0), (0, 1), (0, 1), (0, 0), (1, 0), (2, 1), (2, 1)] - for i in LOOKUP: - block.add_instruction_cjmp(triggers[i[1]], targets[i[0]]) - expected_instructions.append(CJMPInstruction(triggers[i[1]], InstructionPointer(targets[i[0]], 0))) - - expected_compiled_instructions = expected_instructions.copy() - expected_compiled_instructions.append(STOPInstruction()) - self.__verify_block(block, expected_instructions, expected_compiled_instructions, None) - - def test_add_instruction_repj(self) -> None: - block = InstructionBlock() - expected_instructions = [] - targets = [InstructionBlock(), InstructionBlock(), InstructionBlock()] - counts = [3, 8, 857] - LOOKUP = [(0, 0), (0, 1), (1, 1), (0, 2), (2, 0), (1, 0), (2, 2), (2, 1), (1, 0), (1,2)] - for i in LOOKUP: - block.add_instruction_repj(counts[i[0]], targets[i[1]]) - expected_instructions.append(REPJInstruction(counts[i[0]], InstructionPointer(targets[i[1]], 0))) - - expected_compiled_instructions = expected_instructions.copy() - expected_compiled_instructions.append(STOPInstruction()) - self.__verify_block(block, expected_instructions, expected_compiled_instructions, None) - - def test_add_instruction_stop(self) -> None: - block = InstructionBlock() - expected_instructions = [STOPInstruction(), STOPInstruction()] - block.add_instruction_stop() - block.add_instruction_stop() - expected_compiled_instructions = expected_instructions.copy() - expected_compiled_instructions.append(STOPInstruction()) - self.__verify_block(block, expected_instructions, expected_compiled_instructions, None) - - def test_nested_block_construction(self) -> None: - main_block = InstructionBlock() - expected_instructions = [[], [], [], []] - expected_compiled_instructions = [[], [], [], []] - expected_return_ips = [None] - - blocks = [] - - waveforms = [DummyWaveform(), DummyWaveform(), DummyWaveform()] - - main_block.add_instruction_exec(waveforms[0]) - expected_instructions[0].append(EXECInstruction(waveforms[0])) - - block = InstructionBlock() - trigger = Trigger() - ip = InstructionPointer(block) - main_block.add_instruction_cjmp(trigger, block) - expected_instructions[0].append(CJMPInstruction(trigger, ip)) - block.return_ip = InstructionPointer(main_block, len(main_block)) - expected_return_ips.append(InstructionPointer(main_block, len(main_block))) - blocks.append(block) - - block = InstructionBlock() - trigger = Trigger() - ip = InstructionPointer(block) - main_block.add_instruction_cjmp(trigger, block) - expected_instructions[0].append(CJMPInstruction(trigger, ip)) - block.return_ip = InstructionPointer(main_block, len(main_block)) - expected_return_ips.append(InstructionPointer(main_block, len(main_block))) - blocks.append(block) - - WAVEFORM_LOOKUP = [[2, 2, 1, 1],[0, 1, 1, 0, 2, 1]] - for i in [0, 1]: - block = blocks[i] - lookup = WAVEFORM_LOOKUP[i] - for id in lookup: - waveform = waveforms[id] - expected_instructions[i + 1].append(EXECInstruction(waveform)) - block.add_instruction_exec(waveform) - - block = InstructionBlock() - ip = InstructionPointer(block) - blocks[0].add_instruction_cjmp(trigger, block) - expected_instructions[1].append(CJMPInstruction(trigger, ip)) - block.return_ip = InstructionPointer(blocks[0], len(blocks[0])) - expected_return_ips.append(InstructionPointer(blocks[0], len(blocks[0]))) - blocks.append(block) - - for id in [1, 2, 0, 2]: - waveform = waveforms[id] - expected_instructions[3].append(EXECInstruction(waveform)) - block.add_instruction_exec(waveform) - - for i in [0, 1, 2, 3]: - expected_compiled_instructions[i] = expected_instructions[i].copy() - - expected_compiled_instructions[0].append(STOPInstruction()) - for i in [0, 1, 2]: - expected_compiled_instructions[i + 1].append(GOTOInstruction(blocks[i].return_ip)) - - positions = [0, None, None, None] - positions[3] = len(expected_compiled_instructions[1]) - - self.__verify_block(blocks[2], expected_instructions[3], expected_compiled_instructions[3], expected_return_ips[3]) - self.__verify_block(blocks[1], expected_instructions[2], expected_compiled_instructions[2], expected_return_ips[2]) - self.__verify_block(blocks[0], expected_instructions[1], expected_compiled_instructions[1], expected_return_ips[1]) - self.__verify_block(main_block, expected_instructions[0], expected_compiled_instructions[0], expected_return_ips[0]) - - def test_equality(self) -> None: - block1 = InstructionBlock() - block2 = InstructionBlock() - self.assertEqual(block1, block1) - self.assertNotEqual(block1, block2) - self.assertNotEqual(hash(block1), hash(block2)) - - -class ImmutableInstructionBlockTests(unittest.TestCase): - - def __init__(self, method_name: str) -> None: - super().__init__(method_name) - self.maxDiff = None - - def __verify_block(self, - block: AbstractInstructionBlock, - immutable_block: ImmutableInstructionBlock, - context: Dict[AbstractInstructionBlock, ImmutableInstructionBlock]) -> None: - self.assertIsInstance(immutable_block, ImmutableInstructionBlock) - self.assertEqual(len(block.instructions), len(immutable_block.instructions)) - self.assertEqual(len(block), len(immutable_block)) - if block.return_ip is None: - self.assertIsNone(immutable_block.return_ip) - else: - self.assertEqual(InstructionPointer(context[block.return_ip.block], block.return_ip.offset), immutable_block.return_ip) - - for instruction, immutable_instruction in zip(block.instructions, immutable_block.instructions): - self.assertEqual(type(instruction), type(immutable_instruction)) - if isinstance(instruction, (GOTOInstruction, CJMPInstruction, REPJInstruction)): - target_block = instruction.target.block - immutable_target_block = immutable_instruction.target.block - self.assertEqual(instruction.target.offset, immutable_instruction.target.offset) - self.assertIsInstance(immutable_target_block, ImmutableInstructionBlock) - self.assertEqual(context[target_block], immutable_target_block) - self.assertEqual(immutable_block, immutable_target_block.return_ip.block) - self.__verify_block(target_block, immutable_target_block, context) - - def test_empty_unreturning_block(self) -> None: - block = InstructionBlock() - context = dict() - immutable_block = ImmutableInstructionBlock(block, context) - self.__verify_block(block, immutable_block, context.copy()) - - def test_empty_returning_block(self) -> None: - return_block = InstructionBlock() - block = InstructionBlock() - block.return_ip = InstructionPointer(return_block, 7) - context = {return_block: ImmutableInstructionBlock(return_block, dict())} - immutable_block = ImmutableInstructionBlock(block, context) - self.__verify_block(block, immutable_block, context) - - def test_nested_no_context_argument(self) -> None: - parent_block = InstructionBlock() - block = InstructionBlock() - block.return_ip = InstructionPointer(parent_block, 1) - parent_block.add_instruction_goto(block) - immutable_block = ImmutableInstructionBlock(parent_block) - context = { - parent_block: immutable_block, - block: immutable_block.instructions[0].target.block - } - self.__verify_block(parent_block, immutable_block, context) - - def test_nested_cjmp(self) -> None: - parent_block = InstructionBlock() - block = InstructionBlock() - block.return_ip = InstructionPointer(parent_block, 1) - parent_block.add_instruction_cjmp(Trigger(), block) - context = dict() - immutable_block = ImmutableInstructionBlock(parent_block, context) - self.__verify_block(parent_block, immutable_block, context) - - def test_nested_repj(self) -> None: - parent_block = InstructionBlock() - block = InstructionBlock() - block.return_ip = InstructionPointer(parent_block, 1) - parent_block.add_instruction_repj(3, block) - context = dict() - immutable_block = ImmutableInstructionBlock(parent_block, context) - self.__verify_block(parent_block, immutable_block, context) - - def test_nested_goto(self) -> None: - parent_block = InstructionBlock() - block = InstructionBlock() - block.return_ip = InstructionPointer(parent_block, 1) - parent_block.add_instruction_goto(block) - context = dict() - immutable_block = ImmutableInstructionBlock(parent_block, context) - self.__verify_block(parent_block, immutable_block, context) - - def test_multiple_nested_block_construction(self) -> None: - main_block = InstructionBlock() - blocks = [] - waveforms = [DummyWaveform(), DummyWaveform(), DummyWaveform()] - - main_block.add_instruction_exec(waveforms[0]) - - block = InstructionBlock() - trigger = Trigger() - main_block.add_instruction_cjmp(trigger, block) - block.return_ip = InstructionPointer(main_block, len(main_block)) - blocks.append(block) - - block = InstructionBlock() - trigger = Trigger() - main_block.add_instruction_cjmp(trigger, block) - block.return_ip = InstructionPointer(main_block, len(main_block)) - blocks.append(block) - - WAVEFORM_LOOKUP = [[2, 2, 1, 1], [0, 1, 1, 0, 2, 1]] - for i in [0, 1]: - block = blocks[i] - lookup = WAVEFORM_LOOKUP[i] - for id in lookup: - waveform = waveforms[id] - block.add_instruction_exec(waveform) - - block = InstructionBlock() - blocks[0].add_instruction_cjmp(trigger, block) - block.return_ip = InstructionPointer(blocks[0], len(blocks[0])) - blocks.append(block) - - for id in [1, 2, 0, 2]: - waveform = waveforms[id] - block.add_instruction_exec(waveform) - - context = dict() - immutable_block = ImmutableInstructionBlock(main_block, context) - self.__verify_block(main_block, immutable_block, context.copy()) - - -class InstructionStringRepresentation(unittest.TestCase): - def test_str(self) -> None: - IB = InstructionBlock() - T = Trigger() - W = DummyWaveform() - - a = [W, - T, - InstructionPointer(IB,1), - CJMPInstruction(T,IB), - GOTOInstruction(IB), - EXECInstruction(W), - IB - ] - - b = [x.__str__() for x in a] - - for s in b: - self.assertIsInstance(s, str) - -if __name__ == "__main__": - unittest.main(verbosity=2) \ No newline at end of file diff --git a/tests/_program/loop_tests.py b/tests/_program/loop_tests.py index f53083ec4..42d219613 100644 --- a/tests/_program/loop_tests.py +++ b/tests/_program/loop_tests.py @@ -5,9 +5,8 @@ from string import ascii_uppercase from qupulse.utils.types import TimeType, time_from_float -from qupulse._program._loop import Loop, MultiChannelProgram, _make_compatible, _is_compatible, _CompatibilityLevel,\ +from qupulse._program._loop import Loop, _make_compatible, _is_compatible, _CompatibilityLevel,\ RepetitionWaveform, SequenceWaveform, make_compatible, MakeCompatibleWarning -from qupulse._program.instructions import InstructionBlock, ImmutableInstructionBlock from tests.pulses.sequencing_dummies import DummyWaveform from qupulse.pulses.multi_channel_pulse_template import MultiChannelWaveform @@ -34,66 +33,6 @@ def __call__(self): return self.generate_multi_channel_waveform() -def get_two_chan_test_block(wfg=WaveformGenerator(2)): - generate_waveform = wfg.generate_single_channel_waveform - generate_multi_channel_waveform = wfg.generate_multi_channel_waveform - - loop_block11 = InstructionBlock() - loop_block11.add_instruction_exec(generate_multi_channel_waveform()) - - loop_block1 = InstructionBlock() - loop_block1.add_instruction_repj(5, ImmutableInstructionBlock(loop_block11)) - - loop_block21 = InstructionBlock() - loop_block21.add_instruction_exec(generate_multi_channel_waveform()) - loop_block21.add_instruction_exec(generate_multi_channel_waveform()) - - loop_block2 = InstructionBlock() - loop_block2.add_instruction_repj(2, ImmutableInstructionBlock(loop_block21)) - loop_block2.add_instruction_exec(generate_multi_channel_waveform()) - - loop_block3 = InstructionBlock() - loop_block3.add_instruction_exec(generate_multi_channel_waveform()) - loop_block3.add_instruction_exec(generate_multi_channel_waveform()) - - loop_block411 = InstructionBlock() - loop_block411.add_instruction_exec(MultiChannelWaveform([generate_waveform('A')])) - loop_block412 = InstructionBlock() - loop_block412.add_instruction_exec(MultiChannelWaveform([generate_waveform('A')])) - - loop_block41 = InstructionBlock() - loop_block41.add_instruction_repj(7, ImmutableInstructionBlock(loop_block411)) - loop_block41.add_instruction_repj(8, ImmutableInstructionBlock(loop_block412)) - - loop_block421 = InstructionBlock() - loop_block421.add_instruction_exec(MultiChannelWaveform([generate_waveform('B')])) - loop_block422 = InstructionBlock() - loop_block422.add_instruction_exec(MultiChannelWaveform([generate_waveform('B')])) - - loop_block42 = InstructionBlock() - loop_block42.add_instruction_repj(10, ImmutableInstructionBlock(loop_block421)) - loop_block42.add_instruction_repj(11, ImmutableInstructionBlock(loop_block422)) - - chan_block4A = InstructionBlock() - chan_block4A.add_instruction_repj(6, ImmutableInstructionBlock(loop_block41)) - - chan_block4B = InstructionBlock() - chan_block4B.add_instruction_repj(9, ImmutableInstructionBlock(loop_block42)) - - loop_block4 = InstructionBlock() - loop_block4.add_instruction_chan({frozenset('A'): ImmutableInstructionBlock(chan_block4A), - frozenset('B'): ImmutableInstructionBlock(chan_block4B)}) - - root_block = InstructionBlock() - root_block.add_instruction_exec(generate_multi_channel_waveform()) - root_block.add_instruction_repj(10, ImmutableInstructionBlock(loop_block1)) - root_block.add_instruction_repj(17, ImmutableInstructionBlock(loop_block2)) - root_block.add_instruction_repj(3, ImmutableInstructionBlock(loop_block3)) - root_block.add_instruction_repj(4, ImmutableInstructionBlock(loop_block4)) - - return root_block - - @mock.patch.object(Loop, 'MAX_REPR_SIZE', 10000) class LoopTests(unittest.TestCase): def __init__(self, *args, **kwargs): @@ -392,152 +331,6 @@ def test_cleanup_warnings(self): root.cleanup() -class MultiChannelTests(unittest.TestCase): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - wf = DummyWaveform() - self.descriptionA = \ -"""\ -LOOP 1 times: - ->EXEC {} 1 times - ->EXEC {} 50 times - ->LOOP 17 times: - ->LOOP 2 times: - ->EXEC {} 1 times - ->EXEC {} 1 times - ->EXEC {} 1 times - ->LOOP 3 times: - ->EXEC {} 1 times - ->EXEC {} 1 times - ->LOOP 24 times: - ->EXEC {} 7 times - ->EXEC {} 8 times""" - self.descriptionB = \ -"""\ -LOOP 1 times: - ->EXEC {} 1 times - ->EXEC {} 50 times - ->LOOP 17 times: - ->LOOP 2 times: - ->EXEC {} 1 times - ->EXEC {} 1 times - ->EXEC {} 1 times - ->LOOP 3 times: - ->EXEC {} 1 times - ->EXEC {} 1 times - ->LOOP 36 times: - ->EXEC {} 10 times - ->EXEC {} 11 times""" - - def generate_waveform(channel): - return DummyWaveform(sample_output=None, duration=1., defined_channels={channel}) - - def generate_multi_channel_waveform(): - return MultiChannelWaveform([generate_waveform('A'), generate_waveform('B')]) - - self.loop_block11 = InstructionBlock() - self.loop_block11.add_instruction_exec(generate_multi_channel_waveform()) - - self.loop_block1 = InstructionBlock() - self.loop_block1.add_instruction_repj(5, ImmutableInstructionBlock(self.loop_block11)) - - self.loop_block21 = InstructionBlock() - self.loop_block21.add_instruction_exec(generate_multi_channel_waveform()) - self.loop_block21.add_instruction_exec(generate_multi_channel_waveform()) - - self.loop_block2 = InstructionBlock() - self.loop_block2.add_instruction_repj(2, ImmutableInstructionBlock(self.loop_block21)) - self.loop_block2.add_instruction_exec(generate_multi_channel_waveform()) - - self.loop_block3 = InstructionBlock() - self.loop_block3.add_instruction_exec(generate_multi_channel_waveform()) - self.loop_block3.add_instruction_exec(generate_multi_channel_waveform()) - - self.loop_block411 = InstructionBlock() - self.loop_block411.add_instruction_exec(MultiChannelWaveform([generate_waveform('A')])) - self.loop_block412 = InstructionBlock() - self.loop_block412.add_instruction_exec(MultiChannelWaveform([generate_waveform('A')])) - - self.loop_block41 = InstructionBlock() - self.loop_block41.add_instruction_repj(7, ImmutableInstructionBlock(self.loop_block411)) - self.loop_block41.add_instruction_repj(8, ImmutableInstructionBlock(self.loop_block412)) - - self.loop_block421 = InstructionBlock() - self.loop_block421.add_instruction_exec(MultiChannelWaveform([generate_waveform('B')])) - self.loop_block422 = InstructionBlock() - self.loop_block422.add_instruction_exec(MultiChannelWaveform([generate_waveform('B')])) - - self.loop_block42 = InstructionBlock() - self.loop_block42.add_instruction_repj(10, ImmutableInstructionBlock(self.loop_block421)) - self.loop_block42.add_instruction_repj(11, ImmutableInstructionBlock(self.loop_block422)) - - self.chan_block4A = InstructionBlock() - self.chan_block4A.add_instruction_repj(6, ImmutableInstructionBlock(self.loop_block41)) - - self.chan_block4B = InstructionBlock() - self.chan_block4B.add_instruction_repj(9, ImmutableInstructionBlock(self.loop_block42)) - - self.loop_block4 = InstructionBlock() - self.loop_block4.add_instruction_chan({frozenset('A'): ImmutableInstructionBlock(self.chan_block4A), - frozenset('B'): ImmutableInstructionBlock(self.chan_block4B)}) - - self.root_block = InstructionBlock() - self.root_block.add_instruction_exec(generate_multi_channel_waveform()) - self.root_block.add_instruction_repj(10, ImmutableInstructionBlock(self.loop_block1)) - self.root_block.add_instruction_repj(17, ImmutableInstructionBlock(self.loop_block2)) - self.root_block.add_instruction_repj(3, ImmutableInstructionBlock(self.loop_block3)) - self.root_block.add_instruction_repj(4, ImmutableInstructionBlock(self.loop_block4)) - - self.maxDiff = None - - def get_mcp(self, channels): - program = MultiChannelProgram(self.root_block, ['A', 'B']) - return program[channels] - - def test_init(self): - with self.assertRaises(ValueError): - MultiChannelProgram(InstructionBlock()) - - mcp = MultiChannelProgram(self.root_block, ['A', 'B']) - self.assertEqual(mcp.channels, {'A', 'B'}) - - with self.assertRaises(KeyError): - mcp['C'] - - def test_empty_repj(self): - empty_block = InstructionBlock() - - root_block = InstructionBlock() - root_block.add_instruction_repj(1, empty_block) - - with self.assertRaisesRegex(ValueError, 'no defined channels'): - MultiChannelProgram(root_block) - - empty_block.add_instruction_exec(DummyWaveform(duration=1, defined_channels={'A', 'B'})) - MultiChannelProgram(root_block) - - - def test_via_repr(self): - root_loopA = self.get_mcp('A') - root_loopB = self.get_mcp('B') - waveformsA = tuple(loop.waveform - for loop in root_loopA.get_depth_first_iterator() if loop.is_leaf()) - reprA = self.descriptionA.format(*waveformsA) - reprB = self.descriptionB.format(*(loop.waveform - for loop in root_loopB.get_depth_first_iterator() if loop.is_leaf())) - self.assertEqual(root_loopA.__repr__(), reprA) - self.assertEqual(root_loopB.__repr__(), reprB) - - def test_init_from_loop(self): - program = Loop(waveform=DummyWaveform(defined_channels={'A', 'B'})) - - mcp = MultiChannelProgram(program) - self.assertEqual(mcp.programs, {frozenset('AB'): program}) - - with self.assertRaises(TypeError): - MultiChannelProgram(mcp) - - class ProgramWaveformCompatibilityTest(unittest.TestCase): def test_is_compatible_incompatible(self): wf = DummyWaveform(duration=1.1) diff --git a/tests/backward_compatibility/charge_scan_1/binary_program_validation.py b/tests/backward_compatibility/charge_scan_1/binary_program_validation.py index 2f378bacc..c55209fe2 100644 --- a/tests/backward_compatibility/charge_scan_1/binary_program_validation.py +++ b/tests/backward_compatibility/charge_scan_1/binary_program_validation.py @@ -16,8 +16,8 @@ def validate_programs(program_AB, program_CD, loaded_data: dict, parameters): for_C = loaded_data['for_C'] for_D = loaded_data['for_D'] - meas_time_multiplier = parameters["charge_scan___meas_time_multiplier"].get_value() - rep_count = parameters['charge_scan___rep_count'].get_value() + meas_time_multiplier = parameters["charge_scan___meas_time_multiplier"] + rep_count = parameters['charge_scan___rep_count'] expected_samples_A = np.tile(for_A, (meas_time_multiplier * 192, 1, rep_count)).T.ravel() set_ignored_marker_data_to_zero(expected_samples_A) diff --git a/tests/backward_compatibility/tabor_backward_compatibility_tests.py b/tests/backward_compatibility/tabor_backward_compatibility_tests.py index f4c24d04f..97d567232 100644 --- a/tests/backward_compatibility/tabor_backward_compatibility_tests.py +++ b/tests/backward_compatibility/tabor_backward_compatibility_tests.py @@ -9,7 +9,6 @@ from tests.hardware.dummy_devices import DummyDAC from qupulse.serialization import Serializer, FilesystemBackend, PulseStorage -from qupulse.pulses.sequencing import Sequencer from qupulse.pulses.pulse_template import PulseTemplate from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel, MeasurementMask from qupulse.hardware.awgs.tabor import PlottableProgram @@ -100,12 +99,10 @@ def deserialize_pulse_2018(self) -> None: self.pulse = typing.cast(PulseTemplate, pulse_storage[self.pulse_name]) def sequence_pulse(self): - sequencer = Sequencer() - sequencer.push(self.pulse, - parameters=self.parameters, - window_mapping=self.window_mapping, - channel_mapping=self.channel_mapping) - self.program = sequencer.build() + self.program = self.pulse.create_program( + parameters=self.parameters, + measurement_mapping=self.window_mapping, + channel_mapping=self.channel_mapping) def initialize_hardware_setup(self): self.simulator_manager = TaborSimulatorManager() diff --git a/tests/hardware/setup_tests.py b/tests/hardware/setup_tests.py index 02052021b..f951bdb52 100644 --- a/tests/hardware/setup_tests.py +++ b/tests/hardware/setup_tests.py @@ -3,13 +3,12 @@ import numpy as np -from qupulse._program.instructions import InstructionBlock, MEASInstruction from qupulse.hardware.setup import HardwareSetup, PlaybackChannel, MarkerChannel, MeasurementMask from tests.pulses.sequencing_dummies import DummyWaveform from tests.hardware.dummy_devices import DummyAWG, DummyDAC -from tests._program.loop_tests import get_two_chan_test_block, WaveformGenerator +from tests._program.loop_tests import WaveformGenerator class SingleChannelTests(unittest.TestCase): diff --git a/tests/hardware/tabor_tests.py b/tests/hardware/tabor_tests.py index 28874c773..63b33c02e 100644 --- a/tests/hardware/tabor_tests.py +++ b/tests/hardware/tabor_tests.py @@ -6,12 +6,11 @@ from qupulse.hardware.awgs.tabor import TaborException, TaborProgram, \ TaborSegment, TaborSequencing, with_configuration_guard, PlottableProgram -from qupulse._program._loop import MultiChannelProgram, Loop -from qupulse._program.instructions import InstructionBlock +from qupulse._program._loop import Loop from qupulse.hardware.util import voltage_to_uint16 from tests.pulses.sequencing_dummies import DummyWaveform -from tests._program.loop_tests import LoopTests, WaveformGenerator, MultiChannelTests +from tests._program.loop_tests import LoopTests, WaveformGenerator class TaborSegmentTests(unittest.TestCase): diff --git a/tests/pulses/abstract_pulse_template_tests.py b/tests/pulses/abstract_pulse_template_tests.py index 527fa0d4f..78e5e39b8 100644 --- a/tests/pulses/abstract_pulse_template_tests.py +++ b/tests/pulses/abstract_pulse_template_tests.py @@ -31,12 +31,11 @@ def test_invalid_integral(self): AbstractPulseTemplate(identifier='my_apt', integral={'X': 1}, defined_channels={'A'}) def test_declaring(self): - apt = AbstractPulseTemplate(identifier='my_apt', defined_channels={'A'}, is_interruptable=True) + apt = AbstractPulseTemplate(identifier='my_apt', defined_channels={'A'}) self.assertEqual(apt._frozen_properties, set()) - self.assertEqual(apt._declared_properties, {'defined_channels': {'A'}, 'is_interruptable': True}) + self.assertEqual(apt._declared_properties, {'defined_channels': {'A'}}) self.assertEqual(apt.identifier, 'my_apt') - self.assertEqual(apt.is_interruptable, True) def test_freezing(self): apt = AbstractPulseTemplate(identifier='my_apt', defined_channels={'A'}) @@ -113,7 +112,7 @@ def test_method_forwarding(self): args = ([], {}, 'asd') kwargs = {'kw1': [], 'kw2': {}} - forwarded_methods = ['build_sequence', '_create_program', 'requires_stop'] + forwarded_methods = ['_create_program'] for method_name in forwarded_methods: method = getattr(apt, method_name) diff --git a/tests/pulses/arithmetic_pulse_template_tests.py b/tests/pulses/arithmetic_pulse_template_tests.py index 56ffbabcc..874ca2040 100644 --- a/tests/pulses/arithmetic_pulse_template_tests.py +++ b/tests/pulses/arithmetic_pulse_template_tests.py @@ -47,15 +47,7 @@ def test_init(self): self.assertEqual(measurements, arith.measurement_declarations) self.assertEqual('-', arith.arithmetic_operator) self.assertEqual('my_arith', arith.identifier) - - def test_requires_stop(self): - lhs = DummyPulseTemplate(duration=4, defined_channels={'a', 'b'}, parameter_names={'x', 'y'}) - rhs = DummyPulseTemplate(duration=4, defined_channels={'a', 'c'}, parameter_names={'x', 'z'}) - arith = lhs + rhs - with self.assertRaises(NotImplementedError): - arith.requires_stop({}, {}) - def test_build_waveform(self): a = DummyPulseTemplate(duration=4, defined_channels={'a', 'b'}, parameter_names={'x', 'y'}) b = DummyPulseTemplate(duration=4, defined_channels={'a', 'c'}, parameter_names={'x', 'z'}) @@ -441,11 +433,10 @@ def test_integral(self): def test_simple_attributes(self): lhs = DummyPulseTemplate(defined_channels={'a', 'b'}, duration=ExpressionScalar('t_dur'), - is_interruptable=mock.Mock(), measurement_names={'m1'}) + measurement_names={'m1'}) rhs = 4 arith = ArithmeticPulseTemplate(lhs, '+', rhs) self.assertIs(lhs.duration, arith.duration) - self.assertIs(lhs.is_interruptable, arith.is_interruptable) self.assertIs(lhs.measurement_names, arith.measurement_names) def test_parameter_names(self): diff --git a/tests/pulses/conditions_tests.py b/tests/pulses/conditions_tests.py deleted file mode 100644 index 66a441dbc..000000000 --- a/tests/pulses/conditions_tests.py +++ /dev/null @@ -1,163 +0,0 @@ -import unittest -from typing import Optional - -from qupulse._program.instructions import InstructionPointer, Trigger, CJMPInstruction, GOTOInstruction -from qupulse.pulses.conditions import HardwareCondition, SoftwareCondition, ConditionEvaluationException - -from tests.pulses.sequencing_dummies import DummySequencingElement, DummySequencer, DummyInstructionBlock, DummyInstruction - - -class HardwareConditionTest(unittest.TestCase): - def __init__(self, *args, **kwargs): - super().__init__(*args,**kwargs) - self.maxDiff = None - - def test_build_sequence_loop(self) -> None: - sequencer = DummySequencer() - block = DummyInstructionBlock() - block.add_instruction(DummyInstruction()) - - delegator = DummySequencingElement() - body = DummySequencingElement() - - trigger = Trigger() - condition = HardwareCondition(trigger) - condition.build_sequence_loop(delegator, body, sequencer, {}, {}, {}, {}, block) - - self.assertEqual(1, len(block.embedded_blocks)) - body_block = block.embedded_blocks[0] - - self.assertEqual([DummyInstruction(), CJMPInstruction(trigger, InstructionPointer(body_block))], block.instructions, "The expected conditional jump was not generated by HardwareConditon.") - self.assertEqual(InstructionPointer(block, 1), body_block.return_ip, "The return address of the loop body block was set wrongly by HardwareCondition.") - self.assertEqual({body_block: [(body, {}, {}, {}, {})]}, sequencer.sequencing_stacks, "HardwareCondition did not correctly push the body element to the stack") - self.assertFalse(condition.requires_stop()) - - def test_build_sequence_branch(self) -> None: - sequencer = DummySequencer() - block = DummyInstructionBlock() - - delegator = DummySequencingElement() - if_branch = DummySequencingElement() - else_branch = DummySequencingElement() - - trigger = Trigger() - condition = HardwareCondition(trigger) - condition.build_sequence_branch(delegator, if_branch, else_branch, sequencer, {}, {}, {}, {}, instruction_block=block) - - self.assertEqual(2, len(block.embedded_blocks)) - if_block = block.embedded_blocks[0] - else_block = block.embedded_blocks[1] - - self.assertEqual([CJMPInstruction(trigger, InstructionPointer(if_block)), GOTOInstruction(InstructionPointer(else_block))], block.instructions, "The expected jump instruction were not generated by HardwareConditon.") - self.assertEqual(InstructionPointer(block, 2), if_block.return_ip, "The return address of the if branch block was set wrongly by HardwareConditon.") - self.assertEqual(InstructionPointer(block, 2), else_block.return_ip, "The return address of the else branch block was set wrongly by HardwareConditon.") - self.assertEqual({if_block: [(if_branch, {}, {}, {}, {})], else_block: [(else_branch, {}, {}, {}, {})]}, sequencer.sequencing_stacks, "HardwareCondition did not correctly push the branch elements to the stack") - - -class IterationCallbackDummy: - - def __init__(self, callback_return: Optional[bool]) -> None: - super().__init__() - self.callback_return = callback_return - self.loop_iteration = 0 - - def callback(self, loop_iteration: int) -> Optional[bool]: - self.loop_iteration = loop_iteration - return self.callback_return - - -class SoftwareConditionTest(unittest.TestCase): - - def test_build_cannot_evaluate(self) -> None: - sequencer = DummySequencer() - block = DummyInstructionBlock() - - delegator = DummySequencingElement() - body = DummySequencingElement() - - condition = SoftwareCondition(lambda loop_iteration: None) - - self.assertTrue(condition.requires_stop()) - with self.assertRaises(ConditionEvaluationException): - condition.build_sequence_loop(delegator, body, sequencer, {}, {}, {}, {}, block) - with self.assertRaises(ConditionEvaluationException): - condition.build_sequence_branch(delegator, body, body, sequencer, {}, {}, {}, {}, block) - self.assertEqual(str(ConditionEvaluationException()), "The Condition can currently not be evaluated.") - - def test_build_sequence_loop_true(self) -> None: - sequencer = DummySequencer() - block = DummyInstructionBlock() - - delegator = DummySequencingElement() - body = DummySequencingElement() - callback = IterationCallbackDummy(True) - - condition = SoftwareCondition(lambda loop_iteration: callback.callback(loop_iteration)) - condition.build_sequence_loop(delegator, body, sequencer, {}, {}, {}, {}, block) - - self.assertEqual(0, callback.loop_iteration) - self.assertFalse(block.instructions) - self.assertEqual({block: [(delegator, {}, {}, {}, {}), (body, {}, {}, {}, {})]}, sequencer.sequencing_stacks) - - condition.build_sequence_loop(delegator, body, sequencer, {}, {}, {}, {}, block) - self.assertEqual(1, callback.loop_iteration) - - def test_build_sequence_loop_false(self): - sequencer = DummySequencer() - block = DummyInstructionBlock() - - delegator = DummySequencingElement() - body = DummySequencingElement() - callback = IterationCallbackDummy(False) - - condition = SoftwareCondition(lambda loop_iteration: callback.callback(loop_iteration)) - condition.build_sequence_loop(delegator, body, sequencer, {}, {}, {}, {}, block) - - self.assertEqual(0, callback.loop_iteration) - self.assertFalse(block.instructions) - self.assertFalse(sequencer.sequencing_stacks) - - condition.build_sequence_loop(delegator, body, sequencer, {}, {}, {}, {}, block) - self.assertEqual(0, callback.loop_iteration) - - def test_build_sequence_branch_true(self): - sequencer = DummySequencer() - block = DummyInstructionBlock() - - delegator = DummySequencingElement() - if_branch = DummySequencingElement() - else_branch = DummySequencingElement() - callback = IterationCallbackDummy(True) - - condition = SoftwareCondition(lambda loop_iteration: callback.callback(loop_iteration)) - condition.build_sequence_branch(delegator, if_branch, else_branch, sequencer, {}, {}, {}, {}, block) - - self.assertEqual(0, callback.loop_iteration) - self.assertFalse(block.instructions) - self.assertEqual({block: [(if_branch, {}, {}, {}, {})]}, sequencer.sequencing_stacks) - - condition.build_sequence_branch(delegator, if_branch, else_branch, sequencer, {}, {}, {}, {}, block) - self.assertEqual(0, callback.loop_iteration) - - - def test_build_sequence_branch_false(self): - sequencer = DummySequencer() - block = DummyInstructionBlock() - - delegator = DummySequencingElement() - if_branch = DummySequencingElement() - else_branch = DummySequencingElement() - callback = IterationCallbackDummy(False) - - condition = SoftwareCondition(lambda loop_iteration: callback.callback(loop_iteration)) - condition.build_sequence_branch(delegator, if_branch, else_branch, sequencer, {}, {}, {}, {}, block) - - self.assertEqual(0, callback.loop_iteration) - self.assertFalse(block.instructions) - self.assertEqual({block: [(else_branch, {}, {}, {}, {})]}, sequencer.sequencing_stacks) - - condition.build_sequence_branch(delegator, if_branch, else_branch, sequencer, {}, {}, {}, {}, block) - self.assertEqual(0, callback.loop_iteration) - -if __name__ == "__main__": - unittest.main(verbosity=2) \ No newline at end of file diff --git a/tests/pulses/function_pulse_tests.py b/tests/pulses/function_pulse_tests.py index 326e868cb..b33c91e2e 100644 --- a/tests/pulses/function_pulse_tests.py +++ b/tests/pulses/function_pulse_tests.py @@ -57,9 +57,6 @@ def test_expression(self): def test_function_parameters(self): self.assertEqual(self.fpt.function_parameters, {'a', 'b', 'c'}) - def test_is_interruptable(self) -> None: - self.assertFalse(self.fpt.is_interruptable) - def test_defined_channels(self) -> None: self.assertEqual({'A'}, self.fpt.defined_channels) @@ -188,12 +185,6 @@ def test_build_waveform(self) -> None: expected_waveform = FunctionWaveform(expression, duration=duration, channel='B') self.assertEqual(expected_waveform, wf) - def test_requires_stop(self) -> None: - parameters = dict(a=DummyParameter(36.126), z=DummyParameter(247.9543)) - self.assertFalse(self.fpt.requires_stop(parameters, dict())) - parameters = dict(a=DummyParameter(36.126), z=DummyParameter(247.9543, requires_stop=True)) - self.assertTrue(self.fpt.requires_stop(parameters, dict())) - def test_build_waveform_none(self): self.assertIsNone(self.fpt.build_waveform(self.valid_par_vals, channel_mapping={'A': None})) diff --git a/tests/pulses/loop_pulse_template_tests.py b/tests/pulses/loop_pulse_template_tests.py index ba628f68f..1ea6fa4e2 100644 --- a/tests/pulses/loop_pulse_template_tests.py +++ b/tests/pulses/loop_pulse_template_tests.py @@ -2,18 +2,13 @@ from unittest import mock from qupulse.expressions import Expression, ExpressionScalar -from qupulse.pulses.loop_pulse_template import ForLoopPulseTemplate, WhileLoopPulseTemplate,\ - ConditionMissingException, ParametrizedRange, LoopIndexNotUsedException, LoopPulseTemplate +from qupulse.pulses.loop_pulse_template import ForLoopPulseTemplate, ParametrizedRange,\ + LoopIndexNotUsedException, LoopPulseTemplate from qupulse.pulses.parameters import ConstantParameter, InvalidParameterNameException, ParameterConstraintViolation,\ ParameterNotProvidedException, ParameterConstraint -from qupulse._program.instructions import MEASInstruction from qupulse._program._loop import Loop -from qupulse._program._loop import MultiChannelProgram -from qupulse.pulses.sequencing import Sequencer - -from tests.pulses.sequencing_dummies import DummyCondition, DummyPulseTemplate, DummySequencer, DummyInstructionBlock,\ - DummyParameter, MeasurementWindowTestCase, DummyWaveform +from tests.pulses.sequencing_dummies import DummyPulseTemplate, MeasurementWindowTestCase, DummyWaveform from tests.serialization_dummies import DummySerializer from tests.serialization_tests import SerializableTests from tests._program.transformation_tests import TransformationStub @@ -332,17 +327,6 @@ def test_create_program_body_none(self) -> None: self.assertEqual(1, program.repetition_count) self.assertEqual([], program.children) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(flt, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old.repetition_count, program.repetition_count) - self.assertEqual(program_old.children, program.children) - self.assertEqual(program_old.waveform, program.waveform) - # program_old defines measurements while program does not - def test_create_program(self) -> None: dt = DummyPulseTemplate(parameter_names={'i'}, waveform=DummyWaveform(duration=4.0, defined_channels={'A'}), @@ -424,67 +408,6 @@ def test_create_program_append(self) -> None: # which is a use case that does not immediately arise from using Sequencer -class ForLoopTemplateOldSequencingTests(unittest.TestCase): - - def test_build_sequence_constraint_on_loop_var_exception(self): - """This test is to assure the status-quo behavior of ForLoopPT handling parameter constraints affecting the loop index - variable. Please see https://github.com/qutech/qupulse/issues/232 .""" - - with self.assertWarnsRegex(UserWarning, "constraint on a variable shadowing the loop index", - msg="ForLoopPT did not issue a warning when constraining the loop index"): - flt = ForLoopPulseTemplate(body=DummyPulseTemplate(parameter_names={'k', 'i'}), loop_index='i', - loop_range=('a', 'b', 'c',), parameter_constraints=['k<=f', 'k>i']) - - # loop index showing up in parameter_names because it appears in consraints - self.assertEqual(flt.parameter_names, {'f', 'k', 'a', 'b', 'c', 'i'}) - - parameters = {'k': ConstantParameter(1), 'a': ConstantParameter(0), 'b': ConstantParameter(2), - 'c': ConstantParameter(1), 'f': ConstantParameter(2)} - sequencer = DummySequencer() - block = DummyInstructionBlock() - - # loop index not accessible in current build_sequence -> Exception - self.assertRaises(ParameterNotProvidedException, flt.build_sequence, sequencer, parameters, dict(), dict(), dict(), block) - - def test_build_sequence(self): - dt = DummyPulseTemplate(parameter_names={'i'}) - flt = ForLoopPulseTemplate(body=dt, loop_index='i', loop_range=('a', 'b', 'c'), - measurements=[('A', 0, 1)], parameter_constraints=['c > 1']) - - sequencer = DummySequencer() - block = DummyInstructionBlock() - invalid_parameters = {'a': ConstantParameter(1), 'b': ConstantParameter(4), 'c': ConstantParameter(1)} - parameters = {'a': ConstantParameter(1), 'b': ConstantParameter(4), 'c': ConstantParameter(2)} - measurement_mapping = dict(A='B') - channel_mapping = dict(C='D') - - with self.assertRaises(ParameterConstraintViolation): - flt.build_sequence(sequencer, invalid_parameters, dict(), measurement_mapping, channel_mapping, block) - - self.assertEqual(block.instructions, []) - self.assertNotIn(block, sequencer.sequencing_stacks) - - flt.build_sequence(sequencer, parameters, dict(), measurement_mapping, channel_mapping, block) - - self.assertEqual(block.instructions, [MEASInstruction(measurements=[('B', 0, 1)])]) - - expected_stack = [(dt, {'i': ConstantParameter(3)}, dict(), measurement_mapping, channel_mapping), - (dt, {'i': ConstantParameter(1)}, dict(), measurement_mapping, channel_mapping)] - - self.assertEqual(sequencer.sequencing_stacks[block], expected_stack) - - def test_requires_stop(self): - parameters = dict(A=DummyParameter(requires_stop=False), B=DummyParameter(requires_stop=False)) - - dt = DummyPulseTemplate(parameter_names={'i'}) - flt = ForLoopPulseTemplate(body=dt, loop_index='i', loop_range=('A', 'B')) - - self.assertFalse(flt.requires_stop(parameters, dict())) - - parameters['A'] = DummyParameter(requires_stop=True) - self.assertTrue(flt.requires_stop(parameters, dict())) - - class ForLoopPulseTemplateSerializationTests(SerializableTests, unittest.TestCase): @property @@ -614,159 +537,6 @@ def make_dt(ident: str): self.assertEqual([str(c) for c in flt.parameter_constraints], parameter_constraints) -class WhileLoopPulseTemplateTest(unittest.TestCase): - - def test_parameter_names_and_declarations(self) -> None: - condition = DummyCondition() - body = DummyPulseTemplate() - t = WhileLoopPulseTemplate(condition, body) - self.assertEqual(body.parameter_names, t.parameter_names) - - body.parameter_names_ = {'foo', 't', 'bar'} - self.assertEqual(body.parameter_names, t.parameter_names) - - @unittest.skip - def test_is_interruptable(self) -> None: - condition = DummyCondition() - body = DummyPulseTemplate(is_interruptable=False) - t = WhileLoopPulseTemplate(condition, body) - self.assertFalse(t.is_interruptable) - - body.is_interruptable_ = True - self.assertTrue(t.is_interruptable) - - def test_str(self) -> None: - condition = DummyCondition() - body = DummyPulseTemplate() - t = WhileLoopPulseTemplate(condition, body) - self.assertIsInstance(str(t), str) - - def test_integral(self) -> None: - condition = DummyCondition() - body = DummyPulseTemplate(defined_channels={'A', 'B'}) - pulse = WhileLoopPulseTemplate(condition, body) - self.assertEqual({'A': ExpressionScalar('nan'), 'B': ExpressionScalar('nan')}, - pulse.integral) - - -class WhileLoopPulseTemplateSequencingTests(unittest.TestCase): - - def test_requires_stop(self) -> None: - condition = DummyCondition(requires_stop=False) - conditions = {'foo_cond': condition} - body = DummyPulseTemplate(requires_stop=False) - t = WhileLoopPulseTemplate('foo_cond', body) - self.assertFalse(t.requires_stop({}, conditions)) - - condition.requires_stop_ = True - self.assertTrue(t.requires_stop({}, conditions)) - - body.requires_stop_ = True - condition.requires_stop_ = False - self.assertFalse(t.requires_stop({}, conditions)) - - def test_build_sequence(self) -> None: - condition = DummyCondition() - body = DummyPulseTemplate() - t = WhileLoopPulseTemplate('foo_cond', body) - sequencer = DummySequencer() - block = DummyInstructionBlock() - parameters = {} - conditions = {'foo_cond': condition} - measurement_mapping = {'swag': 'aufdrehen'} - channel_mapping = {} - t.build_sequence(sequencer, parameters, conditions, measurement_mapping, channel_mapping, block) - expected_data = dict( - delegator=t, - body=body, - sequencer=sequencer, - parameters=parameters, - conditions=conditions, - measurement_mapping=measurement_mapping, - channel_mapping=channel_mapping, - instruction_block=block - ) - self.assertEqual(expected_data, condition.loop_call_data) - self.assertFalse(condition.branch_call_data) - self.assertFalse(sequencer.sequencing_stacks) - - def test_condition_missing(self) -> None: - body = DummyPulseTemplate(requires_stop=False) - t = WhileLoopPulseTemplate('foo_cond', body) - sequencer = DummySequencer() - block = DummyInstructionBlock() - with self.assertRaises(ConditionMissingException): - t.requires_stop({}, {}) - t.build_sequence(sequencer, {}, {}, {}, block) - - -class WhileLoopPulseTemplateSerializationTests(SerializableTests, unittest.TestCase): - - @property - def class_to_test(self): - return WhileLoopPulseTemplate - - def make_kwargs(self): - return { - 'body': DummyPulseTemplate(), - 'condition': 'foo_cond' - } - - def assert_equal_instance_except_id(self, lhs: WhileLoopPulseTemplate, rhs: WhileLoopPulseTemplate): - self.assertIsInstance(lhs, WhileLoopPulseTemplate) - self.assertIsInstance(rhs, WhileLoopPulseTemplate) - self.assertEqual(lhs.body, rhs.body) - self.assertEqual(lhs.condition, rhs.condition) - - -class WhileLoopPulseTemplateOldSerializationTests(unittest.TestCase): - - def test_get_serialization_data_old(self) -> None: - # test for deprecated version during transition period, remove after final switch - with self.assertWarnsRegex(DeprecationWarning, "deprecated", - msg="WhileLoopPT does not issue warning for old serialization routines."): - body = DummyPulseTemplate() - condition_name = 'foo_cond' - identifier = 'foo_loop' - t = WhileLoopPulseTemplate(condition_name, body, identifier=identifier) - - serializer = DummySerializer() - expected_data = dict(body=str(id(body)), - condition=condition_name) - - data = t.get_serialization_data(serializer) - self.assertEqual(expected_data, data) - - def test_deserialize(self) -> None: - # test for deprecated version during transition period, remove after final switch - with self.assertWarnsRegex(DeprecationWarning, "deprecated", - msg="WhileLoopPT does not issue warning for old serialization routines."): - data = dict( - identifier='foo_loop', - condition='foo_cond', - body='bodyDummyPulse' - ) - - # prepare dependencies for deserialization - serializer = DummySerializer() - serializer.subelements[data['body']] = DummyPulseTemplate() - - # deserialize - result = WhileLoopPulseTemplate.deserialize(serializer, **data) - - # compare - self.assertIs(serializer.subelements[data['body']], result.body) - self.assertEqual(data['condition'], result.condition) - self.assertEqual(data['identifier'], result.identifier) - - -class ConditionMissingExceptionTest(unittest.TestCase): - - def test(self) -> None: - exc = ConditionMissingException('foo') - self.assertIsInstance(str(exc), str) - - class LoopIndexNotUsedExceptionTest(unittest.TestCase): def str_test(self): self.assertEqual(str(LoopIndexNotUsedException('a', {'b', 'c'})), "The parameter a is missing in the body's parameter names: {}".format({'b', 'c'})) diff --git a/tests/pulses/mapping_pulse_template_tests.py b/tests/pulses/mapping_pulse_template_tests.py index 777d14dd4..6c3cbde81 100644 --- a/tests/pulses/mapping_pulse_template_tests.py +++ b/tests/pulses/mapping_pulse_template_tests.py @@ -7,10 +7,9 @@ AmbiguousMappingException, MappingCollisionException from qupulse.pulses.parameters import ConstantParameter, ParameterConstraintViolation, ParameterConstraint, ParameterNotProvidedException from qupulse.expressions import Expression -from qupulse._program._loop import Loop, MultiChannelProgram -from qupulse.pulses.sequencing import Sequencer +from qupulse._program._loop import Loop -from tests.pulses.sequencing_dummies import DummyPulseTemplate, DummySequencer, DummyInstructionBlock, MeasurementWindowTestCase, DummyWaveform +from tests.pulses.sequencing_dummies import DummyPulseTemplate, MeasurementWindowTestCase, DummyWaveform from tests.serialization_tests import SerializableTests from tests.serialization_dummies import DummySerializer from tests._program.transformation_tests import TransformationStub @@ -417,14 +416,6 @@ def test_create_program_subtemplate_none(self) -> None: self.assertEqual(0, len(program.children)) self.assertIsNone(program._measurements) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(st, parameters=pre_parameters, conditions={}, window_mapping=pre_measurement_mapping, - channel_mapping=pre_channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old, program) - def test_same_channel_error(self): dpt = DummyPulseTemplate(defined_channels={'A', 'B'}) @@ -433,39 +424,6 @@ def test_same_channel_error(self): MappingPulseTemplate(dpt, channel_mapping={'A': 'X', 'B': 'X'}) -class MappingPulseTemplateOldSequencingTests(unittest.TestCase): - - def test_build_sequence(self): - measurement_mapping = {'meas1': 'meas2'} - parameter_mapping = {'t': 'k'} - - template = DummyPulseTemplate(measurement_names=set(measurement_mapping.keys()), - parameter_names=set(parameter_mapping.keys())) - st = MappingPulseTemplate(template, parameter_mapping=parameter_mapping, measurement_mapping=measurement_mapping) - sequencer = DummySequencer() - block = DummyInstructionBlock() - pre_parameters = {'k': ConstantParameter(5)} - pre_measurement_mapping = {'meas2': 'meas3'} - pre_channel_mapping = {'default': 'A'} - conditions = dict(a=True) - st.build_sequence(sequencer, pre_parameters, conditions, pre_measurement_mapping, pre_channel_mapping, block) - - self.assertEqual(template.build_sequence_calls, 1) - forwarded_args = template.build_sequence_arguments[0] - self.assertEqual(forwarded_args[0], sequencer) - self.assertEqual(forwarded_args[1], st.map_parameters(pre_parameters)) - self.assertEqual(forwarded_args[2], conditions) - self.assertEqual(forwarded_args[3], - st.get_updated_measurement_mapping(pre_measurement_mapping)) - self.assertEqual(forwarded_args[4], - st.get_updated_channel_mapping(pre_channel_mapping)) - self.assertEqual(forwarded_args[5], block) - - @unittest.skip("Extend of dummy template for argument checking needed.") - def test_requires_stop(self): - pass - - class PulseTemplateParameterMappingExceptionsTests(unittest.TestCase): def test_missing_mapping_exception_str(self) -> None: diff --git a/tests/pulses/measurement_tests.py b/tests/pulses/measurement_tests.py index a1512654c..9c99a9920 100644 --- a/tests/pulses/measurement_tests.py +++ b/tests/pulses/measurement_tests.py @@ -4,8 +4,6 @@ ParameterNotProvidedException, ParameterConstrainer, ConstantParameter from qupulse.pulses.measurement import MeasurementDefiner -from qupulse._program.instructions import InstructionBlock, MEASInstruction - class MeasurementDefinerTest(unittest.TestCase): def __init__(self, *args, to_test_constructor=None, **kwargs): @@ -56,20 +54,6 @@ def test_measurement_windows_invalid(self) -> None: pulse.get_measurement_windows(measurement_mapping=measurement_mapping, parameters=dict(length=10, a=3, d=-1)) - def test_insert_measurement_instruction(self): - pulse = self.to_test_constructor(measurements=[('mw', 'a', 'd')]) - parameters = {'a': ConstantParameter(0), 'd': ConstantParameter(0.9)} - measurement_mapping = {'mw': 'as'} - - block = InstructionBlock() - pulse.insert_measurement_instruction(instruction_block=block, - parameters=parameters, - measurement_mapping=measurement_mapping) - - expected_block = [MEASInstruction([('as', 0, 0.9)])] - self.assertEqual(block.instructions, expected_block) - - def test_none_mappings(self): pulse = self.to_test_constructor(measurements=[('mw', 'a', 'd'), ('asd', 0, 1.)]) diff --git a/tests/pulses/multi_channel_pulse_template_tests.py b/tests/pulses/multi_channel_pulse_template_tests.py index 6f1f2bb19..f0d63ad62 100644 --- a/tests/pulses/multi_channel_pulse_template_tests.py +++ b/tests/pulses/multi_channel_pulse_template_tests.py @@ -8,7 +8,6 @@ TransformingWaveform, ParallelConstantChannelTransformation from qupulse.pulses.parameters import ParameterConstraint, ParameterConstraintViolation, ConstantParameter from qupulse.expressions import ExpressionScalar, Expression -from qupulse._program.instructions import InstructionBlock from qupulse._program.transformation import LinearTransformation, chain_transformations from tests.pulses.sequencing_dummies import DummyPulseTemplate, DummyWaveform @@ -195,18 +194,6 @@ def test_integral(self) -> None: class MultiChannelPulseTemplateSequencingTests(unittest.TestCase): - - def test_requires_stop(self): - sts = [DummyPulseTemplate(duration='t1', defined_channels={'A'}, parameter_names={'a', 'b'}, requires_stop=False), - DummyPulseTemplate(duration='t1', defined_channels={'B'}, parameter_names={'a', 'c'}, requires_stop=False)] - - self.assertFalse(AtomicMultiChannelPulseTemplate(*sts).requires_stop(dict(), dict())) - sts = [ - DummyPulseTemplate(duration='t1', defined_channels={'A'}, parameter_names={'a', 'b'}, requires_stop=False), - DummyPulseTemplate(duration='t1', defined_channels={'B'}, parameter_names={'a', 'c'}, requires_stop=True)] - - self.assertTrue(AtomicMultiChannelPulseTemplate(*sts).requires_stop(dict(), dict())) - def test_build_waveform(self): wfs = [DummyWaveform(duration=1.1, defined_channels={'A'}), DummyWaveform(duration=1.1, defined_channels={'B'})] @@ -260,33 +247,6 @@ def test_build_waveform_none(self): wf = pt.build_waveform(parameters, channel_mapping=channel_mapping) self.assertIsNone(wf) - def test_build_sequence(self): - wfs = [DummyWaveform(duration=1.1, defined_channels={'A'}), DummyWaveform(duration=1.1, defined_channels={'B'})] - sts = [DummyPulseTemplate(duration='t1', defined_channels={'A'}, waveform=wfs[0], measurements=[('m', 0, 1)]), - DummyPulseTemplate(duration='t1', defined_channels={'B'}, waveform=wfs[1]), - DummyPulseTemplate(duration='t1', defined_channels={'C'}, waveform=None)] - - pt = AtomicMultiChannelPulseTemplate(*sts, parameter_constraints=['a < b'], measurements=[('n', .1, .2)]) - - params = dict(a=ConstantParameter(1.0), b=ConstantParameter(1.1)) - measurement_mapping = dict(m='foo', n='bar') - channel_mapping = {'A': 'A', 'B': 'B', 'C': None} - - block = InstructionBlock() - pt.build_sequence(None, parameters=params, conditions={}, measurement_mapping=measurement_mapping, - channel_mapping=channel_mapping, instruction_block=block) - - expected_waveform = MultiChannelWaveform(wfs) - - expected_block = InstructionBlock() - measurements = [('bar', .1, .2), ('foo', 0, 1)] - expected_block.add_instruction_meas(measurements) - expected_block.add_instruction_exec(waveform=expected_waveform) - - self.assertEqual(len(block.instructions), len(expected_block.instructions)) - self.assertEqual(block.instructions[0].compare_key, expected_block.instructions[0].compare_key) - self.assertEqual(block.instructions[1].compare_key, expected_block.instructions[1].compare_key) - def test_get_measurement_windows(self): wfs = [DummyWaveform(duration=1.1, defined_channels={'A'}), DummyWaveform(duration=1.1, defined_channels={'B'})] sts = [DummyPulseTemplate(duration='t1', defined_channels={'A'}, waveform=wfs[0], measurements=[('m', 0, 1), @@ -396,23 +356,11 @@ def test_init(self): self.assertEqual({'a', 'c'}, pccpt.transformation_parameters) self.assertIs(template.duration, pccpt.duration) - template._is_interruptable = mock.Mock() - self.assertIs(pccpt.is_interruptable, template.is_interruptable) - - rs_arg = object() - return_value = object() - template.requires_stop = mock.Mock(return_value=return_value) - self.assertIs(return_value, pccpt.requires_stop(rs_arg)) - template.requires_stop.assert_called_once_with(rs_arg) - def test_missing_implementations(self): pccpt = ParallelConstantChannelPulseTemplate(DummyPulseTemplate(), {}) with self.assertRaises(NotImplementedError): pccpt.get_serialization_data(object()) - with self.assertRaises(NotImplementedError): - pccpt.build_sequence() - def test_integral(self): template = DummyPulseTemplate(duration='t1', defined_channels={'X', 'Y'}, parameter_names={'a', 'b'}, measurement_names={'M'}, diff --git a/tests/pulses/plotting_tests.py b/tests/pulses/plotting_tests.py index f43fe26ee..75d004157 100644 --- a/tests/pulses/plotting_tests.py +++ b/tests/pulses/plotting_tests.py @@ -5,218 +5,15 @@ import numpy -from qupulse.pulses.plotting import PlottingNotPossibleException, render, iter_waveforms, iter_instruction_block, plot -from qupulse._program.instructions import InstructionBlock +from qupulse.pulses.plotting import PlottingNotPossibleException, render, plot from qupulse.pulses.table_pulse_template import TablePulseTemplate from qupulse.pulses.sequence_pulse_template import SequencePulseTemplate -from qupulse.pulses.sequencing import Sequencer -from qupulse._program._loop import MultiChannelProgram, Loop +from qupulse._program._loop import Loop -from tests.pulses.sequencing_dummies import DummyWaveform, DummyInstruction, DummyPulseTemplate +from tests.pulses.sequencing_dummies import DummyWaveform, DummyPulseTemplate class PlotterTests(unittest.TestCase): - - def test_render_unsupported_instructions(self) -> None: - block = InstructionBlock() - block.add_instruction(DummyInstruction()) - - with self.assertRaises(NotImplementedError): - render(block) - - def test_render_no_waveforms(self) -> None: - time, channel_data, measurements = render(InstructionBlock()) - self.assertEqual(channel_data, dict()) - self.assertEqual(measurements, []) - numpy.testing.assert_equal(time, numpy.empty(0)) - - def test_iter_waveforms(self) -> None: - wf1 = DummyWaveform(duration=7) - wf2 = DummyWaveform(duration=5) - wf3 = DummyWaveform(duration=3) - - repeated_block = InstructionBlock() - repeated_block.add_instruction_meas([('m', 1, 2)]) - repeated_block.add_instruction_exec(wf2) - repeated_block.add_instruction_exec(wf1) - - main_block = InstructionBlock() - main_block.add_instruction_exec(wf1) - main_block.add_instruction_repj(2, repeated_block) - main_block.add_instruction_exec(wf3) - - for idx, (expected, received) in enumerate(zip([wf1, wf2, wf1, wf2, wf1, wf3], iter_waveforms(main_block))): - self.assertIs(expected, received, msg="Waveform {} is wrong".format(idx)) - - def test_iter_waveform_exceptions(self) -> None: - wf1 = DummyWaveform(duration=7) - wf2 = DummyWaveform(duration=5) - wf3 = DummyWaveform(duration=3) - - repeated_block = InstructionBlock() - repeated_block.add_instruction_meas([('m', 1, 2)]) - repeated_block.add_instruction_exec(wf2) - repeated_block.add_instruction_exec(wf1) - - main_block = InstructionBlock() - main_block.add_instruction_exec(wf1) - main_block.add_instruction_repj(2, repeated_block) - main_block.add_instruction_exec(wf3) - main_block.add_instruction_goto(repeated_block) - - with self.assertRaises(NotImplementedError): - list(iter_waveforms(main_block)) - - repeated_block.add_instruction(DummyInstruction()) - with self.assertRaises(NotImplementedError): - list(iter_waveforms(main_block)) - - main_block = InstructionBlock() - main_block.add_instruction_stop() - - with self.assertRaises(StopIteration): - next(iter_waveforms(main_block)) - - def test_iter_instruction_block(self) -> None: - wf1 = DummyWaveform(duration=7) - wf2 = DummyWaveform(duration=5) - wf3 = DummyWaveform(duration=3) - - repeated_block = InstructionBlock() - repeated_block.add_instruction_meas([('m', 1, 2)]) - repeated_block.add_instruction_exec(wf2) - repeated_block.add_instruction_exec(wf1) - - main_block = InstructionBlock() - main_block.add_instruction_exec(wf1) - main_block.add_instruction_repj(2, repeated_block) - main_block.add_instruction_exec(wf3) - - waveforms, measurements, total_time = iter_instruction_block(main_block, True) - - for idx, (expected, received) in enumerate(zip([wf1, wf2, wf1, wf2, wf1, wf3], waveforms)): - self.assertIs(expected, received, msg="Waveform {} is wrong".format(idx)) - self.assertEqual([('m', 8, 2), ('m', 20, 2)], measurements) - self.assertEqual(total_time, 34) - - def test_iter_instruction_block_exceptions(self) -> None: - wf1 = DummyWaveform(duration=7) - wf2 = DummyWaveform(duration=5) - wf3 = DummyWaveform(duration=3) - - repeated_block = InstructionBlock() - repeated_block.add_instruction_meas([('m', 1, 2)]) - repeated_block.add_instruction_exec(wf2) - - main_block = InstructionBlock() - main_block.add_instruction_exec(wf1) - main_block.add_instruction_repj(2, repeated_block) - main_block.add_instruction_exec(wf3) - - repeated_block.add_instruction_goto(main_block) - - with self.assertRaises(NotImplementedError): - iter_instruction_block(main_block, False) - - repeated_block = InstructionBlock() - repeated_block.add_instruction_meas([('m', 1, 2)]) - repeated_block.add_instruction_exec(wf2) - repeated_block.add_instruction(DummyInstruction()) - - main_block = InstructionBlock() - main_block.add_instruction_exec(wf1) - main_block.add_instruction_repj(2, repeated_block) - main_block.add_instruction_exec(wf3) - - with self.assertRaises(NotImplementedError): - iter_instruction_block(main_block, False) - - def test_render(self) -> None: - with self.assertWarnsRegex(DeprecationWarning, ".*InstructionBlock.*"): - wf1 = DummyWaveform(duration=19) - wf2 = DummyWaveform(duration=21) - - block = InstructionBlock() - block.add_instruction_exec(wf1) - block.add_instruction_meas([('asd', 0, 1)]) - block.add_instruction_exec(wf2) - - wf1_expected = ('A', [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]) - wf2_expected = ('A', [x-19 for x in [20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40]]) - wf1_output_array_len_expected = len(wf1_expected[1]) - wf2_output_array_len_expected = len(wf2_expected[1]) - - wf1.sample_output = numpy.linspace(start=4, stop=5, num=len(wf1_expected[1])) - wf2.sample_output = numpy.linspace(6, 7, num=len(wf2_expected[1])) - - expected_times = numpy.arange(start=0, stop=42, step=2) - expected_result = numpy.concatenate((wf1.sample_output, wf2.sample_output)) - - times, voltages, _ = render(block, sample_rate=0.5) - - self.assertEqual(len(wf1.sample_calls), 1) - self.assertEqual(len(wf2.sample_calls), 1) - - self.assertEqual(wf1_expected[0], wf1.sample_calls[0][0]) - self.assertEqual(wf2_expected[0], wf2.sample_calls[0][0]) - - numpy.testing.assert_almost_equal(wf1_expected[1], wf1.sample_calls[0][1]) - numpy.testing.assert_almost_equal(wf2_expected[1], wf2.sample_calls[0][1]) - - self.assertEqual(wf1_output_array_len_expected, len(wf1.sample_calls[0][2])) - self.assertEqual(wf2_output_array_len_expected, len(wf2.sample_calls[0][2])) - - self.assertEqual(voltages.keys(), dict(A=0).keys()) - - numpy.testing.assert_almost_equal(expected_times, times) - numpy.testing.assert_almost_equal(expected_result, voltages['A']) - self.assertEqual(expected_result.shape, voltages['A'].shape) - - times, voltages, measurements = render(block, sample_rate=0.5, render_measurements=True) - self.assertEqual(voltages.keys(), dict(A=0).keys()) - - numpy.testing.assert_almost_equal(expected_times, times) - numpy.testing.assert_almost_equal(expected_result, voltages['A']) - self.assertEqual(expected_result.shape, voltages['A'].shape) - - self.assertEqual(measurements, [('asd', 19, 1)]) - - def test_render_block_time_slice(self) -> None: - with self.assertWarnsRegex(DeprecationWarning, ".*InstructionBlock.*"): - with self.assertRaises(ValueError): - wf1 = DummyWaveform(duration=19) - wf2 = DummyWaveform(duration=21) - - block = InstructionBlock() - block.add_instruction_exec(wf1) - block.add_instruction_exec(wf2) - - times, voltages, _ = render(block, sample_rate=0.5, time_slice=(1, 16)) - - def test_render_loop_compare(self) -> None: - wf1 = DummyWaveform(duration=19) - wf2 = DummyWaveform(duration=21) - - block = InstructionBlock() - block.add_instruction_exec(wf1) - block.add_instruction_meas([('asd', 0, 1), ('asd', 1, 1)]) - block.add_instruction_exec(wf2) - - mcp = MultiChannelProgram(block) - loop = next(iter(mcp.programs.values())) - - block_times, block_voltages, _ = render(block, sample_rate=0.5) - loop_times, loop_voltages, _ = render(loop, sample_rate=0.5) - - numpy.testing.assert_equal(block_times, loop_times) - numpy.testing.assert_equal(block_voltages, loop_voltages) - - block_times, block_voltages, block_measurements = render(block, sample_rate=0.5, render_measurements=True) - loop_times, loop_voltages, loop_measurements = render(loop, sample_rate=0.5, render_measurements=True) - numpy.testing.assert_equal(block_times, loop_times) - numpy.testing.assert_equal(block_voltages, loop_voltages) - self.assertEqual(block_measurements, loop_measurements) - def test_render_loop_sliced(self) -> None: wf = DummyWaveform(duration=19) @@ -245,13 +42,10 @@ def test_render_warning(self) -> None: wf1 = DummyWaveform(duration=19) wf2 = DummyWaveform(duration=21) - block = InstructionBlock() - block.add_instruction_exec(wf1) - block.add_instruction_meas([('asd', 0, 1)]) - block.add_instruction_exec(wf2) + program = Loop(children=[Loop(waveform=wf1), Loop(waveform=wf2)]) with self.assertWarns(UserWarning): - render(block, sample_rate=0.51314323423) + render(program, sample_rate=0.51314323423) def integrated_test_with_sequencer_and_pulse_templates(self) -> None: # Setup test data @@ -277,12 +71,10 @@ def integrated_test_with_sequencer_and_pulse_templates(self) -> None: sequence = SequencePulseTemplate([(square, mapping1), (square, mapping1)], outer_parameters) - # run the sequencer and render the plot + # render the plot sample_rate = 20 - sequencer = Sequencer() - sequencer.push(sequence, parameters) - block = sequencer.build() - times, voltages = render(block, sample_rate=sample_rate) + program = sequence.create_program(parameters=parameters) + times, voltages = render(program, sample_rate=sample_rate) # compute expected values expected_times = numpy.linspace(0, 100, sample_rate) @@ -302,10 +94,9 @@ def test_plot_empty_pulse(self) -> None: plot(pt, dict(), show=False) def test_bug_447(self): - """Code from https://github.com/qutech/qupulse/issues/447""" + """Adapted code from https://github.com/qutech/qupulse/issues/447""" TablePT = TablePulseTemplate SequencePT = SequencePulseTemplate - Sequencing = Sequencer period = 8.192004194306148e-05 repetitions = 80 @@ -314,15 +105,12 @@ def test_bug_447(self): table_pt = TablePT({'test': [(0, 0), (period * sec_to_ns, 0, 'linear')]}) - sequencer = Sequencing() template = SequencePT(*((table_pt,) * repetitions)) - channels = template.defined_channels - sequencer.push(template, dict(), channel_mapping={ch: ch for ch in channels}, - window_mapping={w: w for w in template.measurement_names}) - instructions = sequencer.build() + + program = template.create_program() with self.assertWarns(UserWarning): - (_, voltages, _) = render(instructions, sampling_rate / sec_to_ns) + (_, voltages, _) = render(program, sampling_rate / sec_to_ns) class PlottingNotPossibleExceptionTests(unittest.TestCase): diff --git a/tests/pulses/point_pulse_template_tests.py b/tests/pulses/point_pulse_template_tests.py index 96c1a6891..969a240c5 100644 --- a/tests/pulses/point_pulse_template_tests.py +++ b/tests/pulses/point_pulse_template_tests.py @@ -6,7 +6,7 @@ from qupulse.pulses.point_pulse_template import PointPulseTemplate, PointWaveform, InvalidPointDimension, PointPulseEntry, PointWaveformEntry from tests.pulses.measurement_tests import ParameterConstrainerTest, MeasurementDefinerTest -from tests.pulses.sequencing_dummies import DummyParameter, DummyCondition +from tests.pulses.sequencing_dummies import DummyParameter from qupulse.pulses.multi_channel_pulse_template import MultiChannelWaveform from qupulse.pulses.interpolation import HoldInterpolationStrategy, LinearInterpolationStrategy from qupulse.expressions import Expression, ExpressionScalar @@ -99,25 +99,6 @@ def test_integral(self) -> None: class PointPulseTemplateSequencingTests(unittest.TestCase): - - def test_requires_stop_missing_param(self) -> None: - table = PointPulseTemplate([('foo', 'v')], [0]) - with self.assertRaises(ParameterNotProvidedException): - table.requires_stop({'foo': DummyParameter(0, False)}, {}) - - def test_requires_stop(self) -> None: - point = PointPulseTemplate([('foo', 'v'), ('bar', 0)], [0]) - test_sets = [(False, {'foo': DummyParameter(0, False), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, False)}, {'foo': DummyCondition(False)}), - (False, {'foo': DummyParameter(0, False), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, False)}, {'foo': DummyCondition(True)}), - (True, {'foo': DummyParameter(0, True), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, False)}, {'foo': DummyCondition(False)}), - (True, {'foo': DummyParameter(0, True), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, False)}, {'foo': DummyCondition(True)}), - (True, {'foo': DummyParameter(0, False), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, True)}, {'foo': DummyCondition(False)}), - (True, {'foo': DummyParameter(0, False), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, True)}, {'foo': DummyCondition(True)}), - (True, {'foo': DummyParameter(0, True), 'bar': DummyParameter(0, True), 'v': DummyParameter(0, True)}, {'foo': DummyCondition(False)}), - (True, {'foo': DummyParameter(0, True), 'bar': DummyParameter(0, True), 'v': DummyParameter(0, True)}, {'foo': DummyCondition(True)})] - for expected_result, parameter_set, condition_set in test_sets: - self.assertEqual(expected_result, point.requires_stop(parameter_set, condition_set)) - def test_build_waveform_empty(self): self.assertIsNone(PointPulseTemplate([('t1', 'A')], [0]).build_waveform(parameters={'t1': 0, 'A': 1}, channel_mapping={0: 1})) diff --git a/tests/pulses/pulse_template_tests.py b/tests/pulses/pulse_template_tests.py index 6c551b818..904904518 100644 --- a/tests/pulses/pulse_template_tests.py +++ b/tests/pulses/pulse_template_tests.py @@ -6,16 +6,14 @@ from qupulse.utils.types import ChannelID from qupulse.expressions import Expression, ExpressionScalar from qupulse.pulses.pulse_template import AtomicPulseTemplate, PulseTemplate -from qupulse._program.instructions import Waveform, EXECInstruction, MEASInstruction from qupulse.pulses.parameters import Parameter, ConstantParameter, ParameterNotProvidedException from qupulse.pulses.multi_channel_pulse_template import MultiChannelWaveform -from qupulse._program._loop import Loop, MultiChannelProgram +from qupulse._program._loop import Loop from qupulse._program.transformation import Transformation from qupulse._program.waveforms import TransformingWaveform -from qupulse.pulses.sequencing import Sequencer -from tests.pulses.sequencing_dummies import DummyWaveform, DummySequencer, DummyInstructionBlock +from tests.pulses.sequencing_dummies import DummyWaveform from tests._program.transformation_tests import TransformationStub @@ -63,17 +61,8 @@ def duration(self) -> Expression: raise NotImplementedError() return self._duration - def build_sequence(self, - sequencer: "Sequencer", - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition'], - measurement_mapping: Dict[str, str], - channel_mapping: Dict['ChannelID', 'ChannelID'], - instruction_block: 'InstructionBlock'): - raise NotImplementedError() - def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional[Transformation], @@ -81,18 +70,10 @@ def _internal_create_program(self, *, parent_loop: Loop): raise NotImplementedError() - def is_interruptable(self): - raise NotImplementedError() - @property def measurement_names(self): return self._measurement_names - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition']): - raise NotImplementedError() - @property def integral(self) -> Dict[ChannelID, ExpressionScalar]: raise NotImplementedError() @@ -111,9 +92,6 @@ def internal_create_program(*, parameters, parent_loop: Loop, **_): class AtomicPulseTemplateStub(AtomicPulseTemplate): - def is_interruptable(self) -> bool: - return super().is_interruptable() - def __init__(self, *, duration: Expression=None, measurements=None, parameter_names: Optional[Set] = None, identifier: Optional[str]=None, registry=None) -> None: @@ -125,11 +103,6 @@ def __init__(self, *, duration: Expression=None, measurements=None, def build_waveform(self, parameters: Dict[str, Parameter], channel_mapping): raise NotImplementedError() - def requires_stop(self, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition']) -> bool: - return False - @property def defined_channels(self) -> Set['ChannelID']: raise NotImplementedError() @@ -361,48 +334,6 @@ def test_format(self): class AtomicPulseTemplateTests(unittest.TestCase): - def test_is_interruptable(self) -> None: - template = AtomicPulseTemplateStub() - self.assertFalse(template.is_interruptable()) - template = AtomicPulseTemplateStub(identifier="arbg4") - self.assertFalse(template.is_interruptable()) - - def test_build_sequence_no_waveform(self) -> None: - sequencer = DummySequencer() - block = DummyInstructionBlock() - - template = AtomicPulseTemplateStub() - with mock.patch.object(template, 'build_waveform', return_value=None): - template.build_sequence(sequencer, {}, {}, {}, {}, block) - self.assertFalse(block.instructions) - - def test_build_sequence(self) -> None: - measurement_windows = [('M', 0, 5)] - single_wf = DummyWaveform(duration=6, defined_channels={'A'}) - wf = MultiChannelWaveform([single_wf]) - - sequencer = DummySequencer() - block = DummyInstructionBlock() - - parameters = {'a': ConstantParameter(1), 'b': ConstantParameter(2), 'c': ConstantParameter(3)} - expected_parameters = {'a': 1, 'b': 2} - channel_mapping = {'B': 'A'} - - template = AtomicPulseTemplateStub(measurements=measurement_windows, parameter_names={'a', 'b'}) - with mock.patch.object(template, 'build_waveform', return_value=wf) as build_waveform: - template.build_sequence(sequencer, parameters=parameters, conditions={}, - measurement_mapping={'M': 'N'}, channel_mapping=channel_mapping, - instruction_block=block) - build_waveform.assert_called_once_with(parameters=expected_parameters, channel_mapping=channel_mapping) - self.assertEqual(len(block.instructions), 2) - - meas, exec = block.instructions - self.assertIsInstance(meas, MEASInstruction) - self.assertEqual(meas.measurements, [('N', 0, 5)]) - - self.assertIsInstance(exec, EXECInstruction) - self.assertEqual(exec.waveform.defined_channels, {'A'}) - def test_internal_create_program(self) -> None: measurement_windows = [('M', 0, 5)] single_wf = DummyWaveform(duration=6, defined_channels={'A'}) @@ -429,15 +360,6 @@ def test_internal_create_program(self) -> None: self.assertEqual(expected_program, program) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(template, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - with mock.patch.object(template, 'build_waveform', return_value=wf): - block = sequencer.build() - old_program = MultiChannelProgram(block, channels={'A'}) - self.assertEqual(old_program.programs[frozenset({'A'})], program) - def test_internal_create_program_transformation(self): inner_wf = DummyWaveform() template = AtomicPulseTemplateStub(parameter_names=set()) @@ -483,14 +405,6 @@ def test_internal_create_program_no_waveform(self) -> None: self.assertEqual(expected_program, program) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(template, parameters=parameters, conditions={}, window_mapping=measurement_mapping, channel_mapping=channel_mapping) - with mock.patch.object(template, 'build_waveform', return_value=None): - block = sequencer.build() - old_program = MultiChannelProgram(block, channels={'A'}) - self.assertEqual(old_program.programs[frozenset({'A'})], program) - @unittest.skip('not a job of internal_create_program: remove?') def test_internal_create_program_invalid_measurement_mapping(self) -> None: measurement_windows = [('M', 0, 5)] diff --git a/tests/pulses/repetition_pulse_template_tests.py b/tests/pulses/repetition_pulse_template_tests.py index 20cbde970..bd2067eb4 100644 --- a/tests/pulses/repetition_pulse_template_tests.py +++ b/tests/pulses/repetition_pulse_template_tests.py @@ -2,17 +2,13 @@ import warnings from unittest import mock -from qupulse._program._loop import Loop, MultiChannelProgram +from qupulse._program._loop import Loop from qupulse.expressions import Expression -from qupulse.pulses.repetition_pulse_template import RepetitionPulseTemplate,ParameterNotIntegerException, RepetitionWaveform +from qupulse.pulses.repetition_pulse_template import RepetitionPulseTemplate,ParameterNotIntegerException from qupulse.pulses.parameters import ParameterNotProvidedException, ParameterConstraintViolation, ConstantParameter, \ ParameterConstraint -from qupulse._program.instructions import REPJInstruction, InstructionPointer -from qupulse.pulses.sequencing import Sequencer - -from tests.pulses.sequencing_dummies import DummyPulseTemplate, DummySequencer, DummyInstructionBlock, DummyParameter,\ - DummyCondition, DummyWaveform, MeasurementWindowTestCase +from tests.pulses.sequencing_dummies import DummyPulseTemplate, DummyParameter, DummyWaveform, MeasurementWindowTestCase from tests.serialization_dummies import DummySerializer from tests.serialization_tests import SerializableTests from tests._program.transformation_tests import TransformationStub @@ -54,15 +50,6 @@ def test_parameter_names(self) -> None: self.assertEqual({'foo', 'bar', 'hugo', 'd'}, t.parameter_names) - @unittest.skip('is interruptable not implemented for loops') - def test_is_interruptable(self) -> None: - body = DummyPulseTemplate(is_interruptable=False) - t = RepetitionPulseTemplate(body, 6) - self.assertFalse(t.is_interruptable) - - body.is_interruptable_ = True - self.assertTrue(t.is_interruptable) - def test_str(self) -> None: body = DummyPulseTemplate() t = RepetitionPulseTemplate(body, 9) @@ -173,13 +160,6 @@ def test_create_program_constant_success_measurements(self) -> None: self.assert_measurement_windows_equal({'b': ([0, 2, 4], [1, 1, 1]), 'thy': ([2], [2])}, program.get_measurement_windows()) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old, program) - def test_create_program_declaration_success(self) -> None: repetitions = "foo" body = DummyPulseTemplate(duration=2.0, waveform=DummyWaveform(duration=2, defined_channels={'A'})) @@ -271,14 +251,6 @@ def test_create_program_declaration_success_measurements(self) -> None: self.assert_measurement_windows_equal({'fire': ([0], [7.1]), 'b': ([0, 2, 4], [1, 1, 1])}, program.get_measurement_windows()) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old, program) - def test_create_program_declaration_exceeds_bounds(self) -> None: repetitions = "foo" body_program = Loop(waveform=DummyWaveform(duration=1.0)) @@ -414,14 +386,6 @@ def test_create_program_rep_count_zero_constant(self) -> None: self.assertEqual(1, program.repetition_count) self.assertEqual(None, program._measurements) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old, program) - def test_create_program_rep_count_zero_constant_with_measurement(self) -> None: repetitions = 0 body_program = Loop(waveform=DummyWaveform(duration=1.0)) @@ -447,17 +411,6 @@ def test_create_program_rep_count_zero_constant_with_measurement(self) -> None: self.assertEqual(1, program.repetition_count) self.assertEqual(None, program._measurements) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old.repetition_count, program.repetition_count) - self.assertEqual(program_old.waveform, program.waveform) - self.assertEqual(program_old.children, program.children) - # program_old will have measurements which program has not! - def test_create_program_rep_count_zero_declaration(self) -> None: repetitions = "foo" body_program = Loop(waveform=DummyWaveform(duration=1.0)) @@ -482,14 +435,6 @@ def test_create_program_rep_count_zero_declaration(self) -> None: self.assertFalse(program.children) self.assertEqual(1, program.repetition_count) self.assertEqual(None, program._measurements) - - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old, program) def test_create_program_rep_count_zero_declaration_with_measurement(self) -> None: repetitions = "foo" @@ -516,17 +461,6 @@ def test_create_program_rep_count_zero_declaration_with_measurement(self) -> Non self.assertEqual(1, program.repetition_count) self.assertEqual(None, program._measurements) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old.repetition_count, program.repetition_count) - self.assertEqual(program_old.waveform, program.waveform) - self.assertEqual(program_old.children, program.children) - # program_old will have measurements which program has not! - def test_create_program_rep_count_neg_declaration(self) -> None: repetitions = "foo" body_program = Loop(waveform=DummyWaveform(duration=1.0)) @@ -552,14 +486,6 @@ def test_create_program_rep_count_neg_declaration(self) -> None: self.assertEqual(1, program.repetition_count) self.assertEqual(None, program._measurements) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old, program) - def test_create_program_rep_count_neg_declaration_with_measurements(self) -> None: repetitions = "foo" body_program = Loop(waveform=DummyWaveform(duration=1.0)) @@ -585,17 +511,6 @@ def test_create_program_rep_count_neg_declaration_with_measurements(self) -> Non self.assertEqual(1, program.repetition_count) self.assertEqual(None, program._measurements) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old.repetition_count, program.repetition_count) - self.assertEqual(program_old.waveform, program.waveform) - self.assertEqual(program_old.children, program.children) - # program_old will have measurements which program has not! - def test_create_program_none_subprogram(self) -> None: repetitions = "foo" body = DummyPulseTemplate(duration=0.0, waveform=None) @@ -614,17 +529,6 @@ def test_create_program_none_subprogram(self) -> None: self.assertEqual(1, program.repetition_count) self.assertEqual(None, program._measurements) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old.waveform, program.waveform) - self.assertEqual(program_old.children, program.children) - self.assertEqual(program_old._measurements, program._measurements) - # Sequencer does set a repetition count if no inner program is present; create_program does not - def test_create_program_none_subprogram_with_measurement(self) -> None: repetitions = "foo" body = DummyPulseTemplate(duration=2.0, waveform=None, measurements=[('b', 2, 3)]) @@ -643,150 +547,6 @@ def test_create_program_none_subprogram_with_measurement(self) -> None: self.assertEqual(1, program.repetition_count) self.assertEqual(None, program._measurements) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(t, parameters=parameters, conditions={}, window_mapping=measurement_mapping, - channel_mapping=channel_mapping) - block = sequencer.build() - program_old = MultiChannelProgram(block, channels={'A'}).programs[frozenset({'A'})] - self.assertEqual(program_old.waveform, program.waveform) - self.assertEqual(program_old.children, program.children) - # program_old will have measurements which program has not! - # Sequencer does set a repetition count if no inner program is present; create_program does not - - -class RepetitionPulseTemplateOldSequencingTests(unittest.TestCase): - - def setUp(self) -> None: - self.body = DummyPulseTemplate() - self.repetitions = 'foo' - self.template = RepetitionPulseTemplate(self.body, self.repetitions, parameter_constraints=['foo<9']) - self.sequencer = DummySequencer() - self.block = DummyInstructionBlock() - - def test_build_sequence_constant(self) -> None: - repetitions = 3 - t = RepetitionPulseTemplate(self.body, repetitions) - parameters = {} - measurement_mapping = {'my': 'thy'} - conditions = dict(foo=DummyCondition(requires_stop=True)) - channel_mapping = {} - t.build_sequence(self.sequencer, parameters, conditions, measurement_mapping, channel_mapping, self.block) - - self.assertTrue(self.block.embedded_blocks) - body_block = self.block.embedded_blocks[0] - self.assertEqual({body_block}, set(self.sequencer.sequencing_stacks.keys())) - self.assertEqual([(self.body, parameters, conditions, measurement_mapping, channel_mapping)], self.sequencer.sequencing_stacks[body_block]) - self.assertEqual([REPJInstruction(repetitions, InstructionPointer(body_block, 0))], self.block.instructions) - - def test_build_sequence_declaration_success(self) -> None: - parameters = dict(foo=ConstantParameter(3)) - conditions = dict(foo=DummyCondition(requires_stop=True)) - measurement_mapping = dict(moth='fire') - channel_mapping = dict(asd='f') - self.template.build_sequence(self.sequencer, parameters, conditions, measurement_mapping, channel_mapping, self.block) - - self.assertTrue(self.block.embedded_blocks) - body_block = self.block.embedded_blocks[0] - self.assertEqual({body_block}, set(self.sequencer.sequencing_stacks.keys())) - self.assertEqual([(self.body, parameters, conditions, measurement_mapping, channel_mapping)], - self.sequencer.sequencing_stacks[body_block]) - self.assertEqual([REPJInstruction(3, InstructionPointer(body_block, 0))], self.block.instructions) - - def test_parameter_not_provided(self): - parameters = dict(foo=ConstantParameter(4)) - conditions = dict(foo=DummyCondition(requires_stop=True)) - measurement_mapping = dict(moth='fire') - channel_mapping = dict(asd='f') - - template = RepetitionPulseTemplate(self.body, 'foo*bar', parameter_constraints=['foo<9']) - - with self.assertRaises(ParameterNotProvidedException): - template.build_sequence(self.sequencer, parameters, conditions, measurement_mapping, channel_mapping, - self.block) - - def test_build_sequence_declaration_exceeds_bounds(self) -> None: - parameters = dict(foo=ConstantParameter(9)) - conditions = dict(foo=DummyCondition(requires_stop=True)) - with self.assertRaises(ParameterConstraintViolation): - self.template.build_sequence(self.sequencer, parameters, conditions, {}, {}, self.block) - self.assertFalse(self.sequencer.sequencing_stacks) - - def test_build_sequence_declaration_parameter_missing(self) -> None: - parameters = {} - conditions = dict(foo=DummyCondition(requires_stop=True)) - with self.assertRaises(ParameterNotProvidedException): - self.template.build_sequence(self.sequencer, parameters, conditions, {}, {}, self.block) - self.assertFalse(self.sequencer.sequencing_stacks) - - def test_build_sequence_declaration_parameter_value_not_whole(self) -> None: - parameters = dict(foo=ConstantParameter(3.3)) - conditions = dict(foo=DummyCondition(requires_stop=True)) - with self.assertRaises(ParameterNotIntegerException): - self.template.build_sequence(self.sequencer, parameters, conditions, {}, {}, self.block) - self.assertFalse(self.sequencer.sequencing_stacks) - - def test_rep_count_zero_constant(self) -> None: - repetitions = 0 - parameters = {} - measurement_mapping = {} - conditions = {} - channel_mapping = {} - - # suppress warning about 0 repetitions on construction here, we are only interested in correct behavior during sequencing (i.e., do nothing) - with warnings.catch_warnings(record=True): - t = RepetitionPulseTemplate(self.body, repetitions) - t.build_sequence(self.sequencer, parameters, conditions, measurement_mapping, channel_mapping, self.block) - - self.assertFalse(self.block.embedded_blocks) # no new blocks created - self.assertFalse(self.block.instructions) # no instructions added to block - - def test_rep_count_zero_declaration(self) -> None: - t = self.template - parameters = dict(foo=ConstantParameter(0)) - measurement_mapping = {} - conditions = {} - channel_mapping = {} - t.build_sequence(self.sequencer, parameters, conditions, measurement_mapping, channel_mapping, self.block) - - self.assertFalse(self.block.embedded_blocks) # no new blocks created - self.assertFalse(self.block.instructions) # no instructions added to block - - def test_rep_count_neg_declaration(self) -> None: - t = self.template - parameters = dict(foo=ConstantParameter(-1)) - measurement_mapping = {} - conditions = {} - channel_mapping = {} - t.build_sequence(self.sequencer, parameters, conditions, measurement_mapping, channel_mapping, self.block) - - self.assertFalse(self.block.embedded_blocks) # no new blocks created - self.assertFalse(self.block.instructions) # no instructions added to block - - def test_requires_stop_constant(self) -> None: - body = DummyPulseTemplate(requires_stop=False) - t = RepetitionPulseTemplate(body, 2) - self.assertFalse(t.requires_stop({}, {})) - body.requires_stop_ = True - self.assertFalse(t.requires_stop({}, {})) - - def test_requires_stop_declaration(self) -> None: - body = DummyPulseTemplate(requires_stop=False) - t = RepetitionPulseTemplate(body, 'foo') - - parameter = DummyParameter() - parameters = dict(foo=parameter) - condition = DummyCondition() - conditions = dict(foo=condition) - - for body_requires_stop in [True, False]: - for condition_requires_stop in [True, False]: - for parameter_requires_stop in [True, False]: - body.requires_stop_ = body_requires_stop - condition.requires_stop_ = condition_requires_stop - parameter.requires_stop_ = parameter_requires_stop - self.assertEqual(parameter_requires_stop, t.requires_stop(parameters, conditions)) - class RepetitionPulseTemplateSerializationTests(SerializableTests, unittest.TestCase): diff --git a/tests/pulses/sequence_pulse_template_tests.py b/tests/pulses/sequence_pulse_template_tests.py index aceee7960..33194984a 100644 --- a/tests/pulses/sequence_pulse_template_tests.py +++ b/tests/pulses/sequence_pulse_template_tests.py @@ -6,12 +6,9 @@ from qupulse.pulses.sequence_pulse_template import SequencePulseTemplate, SequenceWaveform from qupulse.pulses.mapping_pulse_template import MappingPulseTemplate from qupulse.pulses.parameters import ConstantParameter, ParameterConstraint, ParameterConstraintViolation, ParameterNotProvidedException -from qupulse._program.instructions import MEASInstruction -from qupulse._program._loop import Loop, MultiChannelProgram +from qupulse._program._loop import Loop -from qupulse.pulses.sequencing import Sequencer - -from tests.pulses.sequencing_dummies import DummySequencer, DummyInstructionBlock, DummyPulseTemplate,\ +from tests.pulses.sequencing_dummies import DummyPulseTemplate,\ DummyNoValueParameter, DummyWaveform, MeasurementWindowTestCase from tests.serialization_dummies import DummySerializer from tests.serialization_tests import SerializableTests @@ -283,13 +280,6 @@ def test_create_program_internal(self) -> None: loop.children) self.assert_measurement_windows_equal({'a': ([0], [1]), 'b': ([1], [2])}, loop.get_measurement_windows()) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(seq, parameters=parameters, conditions={}, window_mapping=measurement_mapping, channel_mapping=channel_mapping) - block = sequencer.build() - old_program = MultiChannelProgram(block, channels={'A'}) - self.assertEqual(old_program.programs[frozenset({'A'})], loop) - ### test again with inverted sequence seq = SequencePulseTemplate(sub2, sub1, measurements=[('a', 0, 1)]) loop = Loop() @@ -306,13 +296,6 @@ def test_create_program_internal(self) -> None: loop.children) self.assert_measurement_windows_equal({'a': ([0], [1]), 'b': ([3], [2])}, loop.get_measurement_windows()) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(seq, parameters=parameters, conditions={}, window_mapping=measurement_mapping, channel_mapping=channel_mapping) - block = sequencer.build() - old_program = MultiChannelProgram(block, channels={'A'}) - self.assertEqual(old_program.programs[frozenset({'A'})], loop) - def test_internal_create_program_no_measurement_mapping(self) -> None: sub1 = DummyPulseTemplate(duration=3, waveform=DummyWaveform(duration=3), measurements=[('b', 1, 2)]) sub2 = DummyPulseTemplate(duration=2, waveform=DummyWaveform(duration=2), parameter_names={'foo'}) @@ -365,13 +348,6 @@ def test_internal_create_program_one_child_no_duration(self) -> None: loop.children) self.assert_measurement_windows_equal({'a': ([0], [1])}, loop.get_measurement_windows()) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(seq, parameters=parameters, conditions={}, window_mapping=measurement_mapping, channel_mapping=channel_mapping) - block = sequencer.build() - old_program = MultiChannelProgram(block, channels={'A'}) - self.assertEqual(old_program.programs[frozenset({'A'})], loop) - ### test again with inverted sequence seq = SequencePulseTemplate(sub2, sub1, measurements=[('a', 0, 1)]) loop = Loop() @@ -387,13 +363,6 @@ def test_internal_create_program_one_child_no_duration(self) -> None: loop.children) self.assert_measurement_windows_equal({'a': ([0], [1])}, loop.get_measurement_windows()) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(seq, parameters=parameters, conditions={}, window_mapping=measurement_mapping, channel_mapping=channel_mapping) - block = sequencer.build() - old_program = MultiChannelProgram(block, channels={'A'}) - self.assertEqual(old_program.programs[frozenset({'A'})], loop) - def test_internal_create_program_both_children_no_duration(self) -> None: sub1 = DummyPulseTemplate(duration=0, waveform=None, measurements=[('b', 1, 2)], defined_channels={'A'}) sub2 = DummyPulseTemplate(duration=0, waveform=None, parameter_names={'foo'}, defined_channels={'A'}) @@ -414,16 +383,6 @@ def test_internal_create_program_both_children_no_duration(self) -> None: self.assertEqual([], loop.children) self.assertIsNone(loop._measurements) - # ensure same result as from Sequencer - sequencer = Sequencer() - sequencer.push(seq, parameters=parameters, conditions={}, window_mapping=measurement_mapping, channel_mapping=channel_mapping) - block = sequencer.build() - old_program = MultiChannelProgram(block, channels={'A'}) - old_loop = old_program.programs[frozenset({'A'})] - self.assertEqual(old_loop.waveform, loop.waveform) - self.assertEqual(old_loop.children, loop.children) - # new loop will have no measurements. old_loop still defines SequencePT measurements - def test_internal_create_program_parameter_constraint_violations(self) -> None: sub1 = DummyPulseTemplate(duration=3, waveform=DummyWaveform(duration=3), measurements=[('b', 1, 2)]) sub2 = DummyPulseTemplate(duration=2, waveform=DummyWaveform(duration=2), parameter_names={'foo'}) @@ -475,107 +434,7 @@ def test_internal_create_program_parameter_missing(self) -> None: parent_loop=loop) -class SequencePulseTemplateOldSequencingTests(SequencePulseTemplateTest): - def test_build_sequence(self) -> None: - sub1 = DummyPulseTemplate(requires_stop=False) - sub2 = DummyPulseTemplate(requires_stop=True, parameter_names={'foo'}) - parameters = {'foo': DummyNoValueParameter()} - - sequencer = DummySequencer() - block = DummyInstructionBlock() - seq = SequencePulseTemplate(sub1, (sub2, {'foo': 'foo'}), measurements=[('a', 0, 1)]) - seq.build_sequence(sequencer, parameters, - conditions=dict(), - channel_mapping={'default': 'a'}, - measurement_mapping={'a': 'b'}, - instruction_block=block) - self.assertEqual(2, len(sequencer.sequencing_stacks[block])) - - self.assertEqual(block.instructions[0], MEASInstruction([('b', 0, 1)])) - - sequencer = DummySequencer() - block = DummyInstructionBlock() - seq = SequencePulseTemplate((sub2, {'foo': 'foo'}), sub1) - seq.build_sequence(sequencer, parameters, {}, {}, {}, block) - self.assertEqual(2, len(sequencer.sequencing_stacks[block])) - - @unittest.skip("Was this test faulty before? Why should the three last cases return false?") - def test_requires_stop(self) -> None: - sub1 = (DummyPulseTemplate(requires_stop=False), {}, {}) - sub2 = (DummyPulseTemplate(requires_stop=True, parameter_names={'foo'}), {'foo': 'foo'}, {}) - parameters = {'foo': DummyNoValueParameter()} - - seq = SequencePulseTemplate(sub1) - self.assertFalse(seq.requires_stop(parameters, {})) - - seq = SequencePulseTemplate(sub2) - self.assertFalse(seq.requires_stop(parameters, {})) - - seq = SequencePulseTemplate(sub1, sub2) - self.assertFalse(seq.requires_stop(parameters, {})) - - seq = SequencePulseTemplate(sub2, sub1) - self.assertFalse(seq.requires_stop(parameters, {})) - - def test_crash(self) -> None: - table = TablePulseTemplate({'default': [('ta', 'va', 'hold'), - ('tb', 'vb', 'linear'), - ('tend', 0, 'jump')]}, identifier='foo') - - expected_parameters = {'ta', 'tb', 'tc', 'td', 'va', 'vb', 'tend'} - first_mapping = { - 'ta': 'ta', - 'tb': 'tb', - 'va': 'va', - 'vb': 'vb', - 'tend': 'tend' - } - second_mapping = { - 'ta': 'tc', - 'tb': 'td', - 'va': 'vb', - 'vb': 'va + vb', - 'tend': '2 * tend' - } - sequence = SequencePulseTemplate((table, first_mapping, {}), (table, second_mapping, {})) - self.assertEqual(expected_parameters, sequence.parameter_names) - - parameters = { - 'ta': ConstantParameter(2), - 'va': ConstantParameter(2), - 'tb': ConstantParameter(4), - 'vb': ConstantParameter(3), - 'tc': ConstantParameter(5), - 'td': ConstantParameter(11), - 'tend': ConstantParameter(6)} - - sequencer = DummySequencer() - block = DummyInstructionBlock() - self.assertFalse(sequence.requires_stop(parameters, {})) - sequence.build_sequence(sequencer, - parameters=parameters, - conditions={}, - measurement_mapping={}, - channel_mapping={'default': 'default'}, - instruction_block=block) - from qupulse.pulses.sequencing import Sequencer - s = Sequencer() - s.push(sequence, parameters, channel_mapping={'default': 'EXAMPLE_A'}) - - class SequencePulseTemplateTestProperties(SequencePulseTemplateTest): - def test_is_interruptable(self): - - self.assertTrue( - SequencePulseTemplate(DummyPulseTemplate(is_interruptable=True), - DummyPulseTemplate(is_interruptable=True)).is_interruptable) - self.assertTrue( - SequencePulseTemplate(DummyPulseTemplate(is_interruptable=True), - DummyPulseTemplate(is_interruptable=False)).is_interruptable) - self.assertFalse( - SequencePulseTemplate(DummyPulseTemplate(is_interruptable=False), - DummyPulseTemplate(is_interruptable=False)).is_interruptable) - def test_measurement_names(self): d1 = DummyPulseTemplate(measurement_names={'a'}) d2 = DummyPulseTemplate(measurement_names={'b'}) diff --git a/tests/pulses/sequencing_and_create_program_tests.py b/tests/pulses/sequencing_and_create_program_tests.py deleted file mode 100644 index 94f96c6bf..000000000 --- a/tests/pulses/sequencing_and_create_program_tests.py +++ /dev/null @@ -1,75 +0,0 @@ -import unittest -from unittest import mock -from typing import Dict, Union - -from qupulse.pulses.pulse_template import PulseTemplate -from qupulse.pulses.sequencing import Sequencer -from qupulse.pulses import TablePT, FunctionPT, AtomicMultiChannelPT, ForLoopPT, RepetitionPT, SequencePT, MappingPT - -from qupulse._program._loop import Loop, MultiChannelProgram - - -class SequencingCompatibilityTest: - def get_pulse_template(self) -> PulseTemplate: - raise NotImplementedError() - - def get_parameters(self) -> dict: - raise NotImplementedError() - - def get_channel_mapping(self) -> Dict[str, str]: - raise NotImplementedError() - - def get_measurement_mapping(self) -> Dict[str, str]: - raise NotImplementedError() - - def build_program_with_sequencer(self: unittest.TestCase, pulse_template, measurement_mapping=None, **kwargs): - sequencer = Sequencer() - sequencer.push(sequencing_element=pulse_template, conditions=dict(), **kwargs, window_mapping=measurement_mapping) - instruction_block = sequencer.build() - mcp = MultiChannelProgram(instruction_block=instruction_block) - self.assertEqual(len(mcp.programs), 1) - return next(iter(mcp.programs.values())) - - def build_program_with_create_program(self, pulse_template: PulseTemplate, **kwargs): - return pulse_template.create_program(**kwargs) - - def assert_results_in_exact_same_program(self: Union[unittest.TestCase, 'SequencingCompatibilityTest'], **kwargs): - pt = self.get_pulse_template() - - seq_program = self.build_program_with_sequencer(pt, **kwargs) - cre_program = self.build_program_with_create_program(pt, **kwargs) - - self.assertEqual(seq_program, cre_program) - - def test_exact_same_program(self): - self.assert_results_in_exact_same_program(parameters=self.get_parameters(), - channel_mapping=self.get_channel_mapping(), - measurement_mapping=self.get_measurement_mapping()) - - -class ComplexProgramSequencingCompatibilityTest(SequencingCompatibilityTest, unittest.TestCase): - def get_pulse_template(self) -> PulseTemplate: - fpt = FunctionPT('sin(omega*t)', 't_duration', 'X', measurements=[('M', 0, 't_duration')]) - tpt = TablePT({'Y': [(0, 'a'), ('t_duration', 2)], - 'Z': [('t_duration', 1)]}, measurements=[('N', 0, 't_duration/2')]) - mpt = MappingPT(fpt, parameter_mapping={'omega': '2*pi/t_duration'}, allow_partial_parameter_mapping=True) - - ampt = AtomicMultiChannelPT(mpt, tpt) - body = ampt @ ampt - rpt = RepetitionPT(body, 'N_rep', measurements=[('O', 0, 1)]) - - forpt = ForLoopPT(rpt, 'a', '6') - - final = SequencePT(rpt, forpt) - return final - - def get_parameters(self) -> dict: - return dict(t_duration=4, - N_rep=17, - a=-1) - - def get_channel_mapping(self) -> Dict[str, str]: - return {'X': 'A', 'Z': None, 'Y': 'B'} - - def get_measurement_mapping(self) -> Dict[str, str]: - return {'M': 'S', 'N': None, 'O': 'T'} diff --git a/tests/pulses/sequencing_dummies.py b/tests/pulses/sequencing_dummies.py index c5a86f810..9b536bb62 100644 --- a/tests/pulses/sequencing_dummies.py +++ b/tests/pulses/sequencing_dummies.py @@ -10,12 +10,9 @@ from qupulse.utils.types import MeasurementWindow, ChannelID, TimeType, time_from_float from qupulse.serialization import Serializer from qupulse._program.waveforms import Waveform -from qupulse._program.instructions import Instruction, CJMPInstruction, GOTOInstruction, REPJInstruction -from qupulse.pulses.sequencing import Sequencer, InstructionBlock, SequencingElement from qupulse.pulses.parameters import Parameter from qupulse.pulses.pulse_template import AtomicPulseTemplate from qupulse.pulses.interpolation import InterpolationStrategy -from qupulse.pulses.conditions import Condition from qupulse.expressions import Expression, ExpressionScalar @@ -74,77 +71,6 @@ def deserialize(cls, serializer: Optional[Serializer]=None) -> 'DummyParameter': def __hash__(self): return 0 -class DummySequencingElement(SequencingElement): - - def __init__(self, requires_stop: bool = False, push_elements: Tuple[InstructionBlock, List[SequencingElement]] = None) -> None: - super().__init__() - self.build_call_counter = 0 - self.requires_stop_call_counter = 0 - self.target_block = None - self.parameters = None - self.conditions = None - self.window_mapping = None - self.channel_mapping = None - self.requires_stop_ = requires_stop - self.push_elements = push_elements - self.parameter_names = set() - self.condition_names = set() - self.atomicity_ = False - - def build_sequence(self, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition'], - measurement_mapping: Optional[Dict[str, str]], - channel_mapping: Dict['ChannelID', 'ChannelID'], - instruction_block: InstructionBlock) -> None: - self.build_call_counter += 1 - self.target_block = instruction_block - instruction_block.add_instruction(DummyInstruction(self)) - self.parameters = parameters - self.conditions = conditions - self.window_mapping = measurement_mapping - self.channel_mapping = channel_mapping - if self.push_elements is not None: - for element in self.push_elements[1]: - sequencer.push(element, parameters, conditions, - window_mapping=measurement_mapping, - channel_mapping=channel_mapping, - target_block=self.push_elements[0]) - - def requires_stop(self, parameters: Dict[str, Parameter], conditions: Dict[str, 'Conditions']) -> bool: - self.requires_stop_call_counter += 1 - self.parameters = parameters - self.conditions = conditions - return self.requires_stop_ - - @property - def atomicity(self): - return self.atomicity_ - - -class DummyInstruction(Instruction): - - def __init__(self, elem: DummySequencingElement = None) -> None: - super().__init__() - self.elem = elem - - @property - def compare_key(self) -> Any: - return self.elem - - -class DummyInstructionBlock(InstructionBlock): - - def __init__(self) -> None: - super().__init__() - self.embedded_blocks = [] # type: Collection[InstructionBlock] - - def add_instruction(self, instruction: Instruction) -> None: - super().add_instruction(instruction) - if isinstance(instruction, (CJMPInstruction, GOTOInstruction, REPJInstruction)): - self.embedded_blocks.append(instruction.target.block) - class DummyWaveform(Waveform): @@ -216,35 +142,6 @@ def defined_channels(self): return self.defined_channels_ -class DummySequencer(Sequencer): - - def __init__(self) -> None: - super().__init__() - self.sequencing_stacks = {} #type: Dict[InstructionBlock, List[StackElement]] - - def push(self, - sequencing_element: SequencingElement, - parameters: Dict[str, Parameter], - conditions: Dict[str, 'Condition'], - window_mapping: Optional[Dict[str, str]] = None, - channel_mapping: Dict['ChannelID', 'ChannelID'] = dict(), - target_block: InstructionBlock = None) -> None: - if target_block is None: - target_block = self.__main_block - - if target_block not in self.sequencing_stacks: - self.sequencing_stacks[target_block] = [] - - self.sequencing_stacks[target_block].append((sequencing_element, parameters, conditions, window_mapping, - channel_mapping)) - - def build(self) -> InstructionBlock: - raise NotImplementedError() - - def has_finished(self): - raise NotImplementedError() - - class DummyInterpolationStrategy(InterpolationStrategy): def __init__(self) -> None: @@ -266,65 +163,10 @@ def expression(self) -> ExpressionScalar: raise NotImplementedError() -class DummyCondition(Condition): - - def __init__(self, requires_stop: bool=False): - super().__init__() - self.requires_stop_ = requires_stop - self.loop_call_data = {} - self.branch_call_data = {} - - def requires_stop(self) -> bool: - return self.requires_stop_ - - def build_sequence_loop(self, - delegator: SequencingElement, - body: SequencingElement, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict['ChannelID', 'ChannelID'], - instruction_block: InstructionBlock) -> None: - self.loop_call_data = dict( - delegator=delegator, - body=body, - sequencer=sequencer, - parameters=parameters, - conditions=conditions, - measurement_mapping=measurement_mapping, - channel_mapping=channel_mapping, - instruction_block=instruction_block - ) - - def build_sequence_branch(self, - delegator: SequencingElement, - if_branch: SequencingElement, - else_branch: SequencingElement, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict['ChannelID', 'ChannelID'], - instruction_block: InstructionBlock) -> None: - self.branch_call_data = dict( - delegator=delegator, - if_branch=if_branch, - else_branch=else_branch, - sequencer=sequencer, - parameters=parameters, - conditions=conditions, - measurement_mapping=measurement_mapping, - channel_mapping=channel_mapping, - instruction_block=instruction_block - ) - - class DummyPulseTemplate(AtomicPulseTemplate): def __init__(self, requires_stop: bool=False, - is_interruptable: bool=False, parameter_names: Set[str]={}, defined_channels: Set[ChannelID]={'default'}, duration: Any=0, @@ -339,9 +181,7 @@ def __init__(self, self.requires_stop_ = requires_stop self.requires_stop_arguments = [] - self.is_interruptable_ = is_interruptable self.parameter_names_ = parameter_names - self.build_sequence_arguments = [] self.defined_channels_ = defined_channels self._duration = Expression(duration) self.waveform = waveform @@ -360,14 +200,6 @@ def duration(self): def parameter_names(self) -> Set[str]: return set(self.parameter_names_) - @property - def build_sequence_calls(self): - return len(self.build_sequence_arguments) - - @property - def is_interruptable(self) -> bool: - return self.is_interruptable_ - @property def defined_channels(self) -> Set[ChannelID]: return set(self.defined_channels_) @@ -376,28 +208,8 @@ def defined_channels(self) -> Set[ChannelID]: def measurement_names(self) -> Set[str]: return self.measurement_names_ - def build_sequence(self, - sequencer: Sequencer, - parameters: Dict[str, Parameter], - conditions: Dict[str, Condition], - measurement_mapping: Dict[str, str], - channel_mapping: Dict['ChannelID', 'ChannelID'], - instruction_block: InstructionBlock): - self.build_sequence_arguments.append((sequencer,parameters,conditions, measurement_mapping, channel_mapping, instruction_block)) - measurements = self.get_measurement_windows(parameters, measurement_mapping) - if self.waveform: - instruction_block.add_instruction_meas(measurements) - instruction_block.add_instruction_exec(waveform=self.waveform) - - # def create_program(self, *, - # parameters: Dict[str, Parameter], - # measurement_mapping: Dict[str, Optional[str]], - # channel_mapping: Dict[ChannelID, Optional[ChannelID]]) -> Optional[Loop]: - # self.create_program_calls.append((parameters, measurement_mapping, channel_mapping)) - # return self._program - def _internal_create_program(self, *, - parameters: Dict[str, Parameter], + scope: Scope, measurement_mapping: Dict[str, Optional[str]], channel_mapping: Dict[ChannelID, Optional[ChannelID]], global_transformation: Optional['Transformation'], @@ -420,16 +232,10 @@ def build_waveform(self, return self.waveform return DummyWaveform(duration=self.duration.evaluate_numeric(**parameters), defined_channels=self.defined_channels) - def requires_stop(self, parameters: Dict[str, Parameter], conditions: Dict[str, Condition]) -> bool: - self.requires_stop_arguments.append((parameters,conditions)) - return self.requires_stop_ - def get_serialization_data(self, serializer: Optional['Serializer']=None) -> Dict[str, Any]: data = super().get_serialization_data(serializer=serializer) if serializer: # compatibility with old serialization routines data = dict() - data['requires_stop'] = self.requires_stop_ - data['is_interruptable'] = self.is_interruptable data['parameter_names'] = self.parameter_names data['defined_channels'] = self.defined_channels data['duration'] = self.duration @@ -443,5 +249,5 @@ def integral(self) -> Dict[ChannelID, ExpressionScalar]: @property def compare_key(self) -> Tuple[Any, ...]: - return (self.requires_stop_, self.is_interruptable, self.parameter_names, + return (self.requires_stop_, self.parameter_names, self.defined_channels, self.duration, self.waveform, self.measurement_names, self.integral) diff --git a/tests/pulses/sequencing_tests.py b/tests/pulses/sequencing_tests.py deleted file mode 100644 index d00eb009b..000000000 --- a/tests/pulses/sequencing_tests.py +++ /dev/null @@ -1,630 +0,0 @@ -import unittest - -from qupulse.pulses.parameters import ConstantParameter -from qupulse._program.instructions import InstructionBlock, STOPInstruction -from qupulse.pulses.sequencing import Sequencer - -from tests.pulses.sequencing_dummies import DummySequencingElement, DummyCondition - - -class SequencerTest(unittest.TestCase): - - def test_initialization(self) -> None: - sequencer = Sequencer() - self.assertTrue(sequencer.has_finished()) - - def test_push(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo' : 'bar'} - elem = DummySequencingElement() - - sequencer.push(elem, ps, cs, window_mapping=wm) - self.assertFalse(sequencer.has_finished()) - sequencer.build() - self.assertEqual(ps, elem.parameters) - - def test_push_float_params(self) -> None: - sequencer = Sequencer() - - ps = {'foo': 1, 'bar': 7.3} - cs = {'foo': DummyCondition()} - elem = DummySequencingElement() - sequencer.push(elem, ps, cs) - self.assertFalse(sequencer.has_finished()) - sequencer.build() - self.assertIsInstance(elem.parameters['foo'], ConstantParameter) - self.assertIsInstance(elem.parameters['bar'], ConstantParameter) - self.assertEqual(cs, elem.conditions) - -# The following are methods to test different execution path through the build() method. -# Most relevant paths with up to 2 iterations for each loop are covered, excluding "mirror" configurations. -# Note that there are paths which can never occur and thus are not tested. -# Example for naming: o2_m1_i2_tf_m2_i1_f_i1_f -# The outermost loop is iterated twice (o2) -# - In the first iteration, the middle loop is iterated once (m1) -# - Therein, the inner loop is iterated twice (i2) with branching decisions true (first iteration) and false (second iteration) (tf) -# - In the second iteration, the middle loop is iterated twice (m2) -# - In its first iteration, the inner loop is iterated once (i1) with branching decision false (f) -# - In its second iteration, the inner loop is iterated once (i1) with branching decision false (f) - - - def test_build_path_no_loop_nothing_to_do(self) -> None: - sequencer = Sequencer() - - sequencer.build() - self.assertTrue(sequencer.has_finished()) - - def test_build_path_o1_m1_i1_f_single_element_requires_stop_main_block(self) -> None: - sequencer = Sequencer() - - elem = DummySequencingElement(True) - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - sequencer.push(elem, ps, cs) - sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertEqual(ps, elem.parameters) - self.assertEqual(cs, elem.conditions) - self.assertEqual(1, elem.requires_stop_call_counter) - self.assertEqual(0, elem.build_call_counter) - - def test_build_path_o1_m2_i1_f_i0_one_element_custom_block_requires_stop(self) -> None: - sequencer = Sequencer() - - elem = DummySequencingElement(True) - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {} - target_block = InstructionBlock() - sequencer.push(elem, ps, cs, window_mapping=wm, target_block=target_block) - sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertEqual(ps, elem.parameters) - self.assertEqual(cs, elem.conditions) - self.assertEqual(1, elem.requires_stop_call_counter) - self.assertEqual(0, elem.build_call_counter) - - def test_build_path_o1_m2_i1_f_i1_f_one_element_custom_and_main_block_requires_stop(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {} - cm = {} - elem_main = DummySequencingElement(True) - sequencer.push(elem_main, ps, cs, channel_mapping=cm) - - elem_cstm = DummySequencingElement(True) - target_block = InstructionBlock() - sequencer.push(elem_cstm, ps, cs, window_mapping=wm, channel_mapping=cm, target_block=target_block) - - sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertEqual(ps, elem_main.parameters) - self.assertEqual(cs, elem_main.conditions) - self.assertEqual(1, elem_main.requires_stop_call_counter) - self.assertEqual(0, elem_main.build_call_counter) - self.assertEqual(ps, elem_cstm.parameters) - self.assertEqual(cs, elem_cstm.conditions) - self.assertEqual(1, elem_cstm.requires_stop_call_counter) - self.assertEqual(0, elem_cstm.build_call_counter) - - def test_build_path_o2_m1_i1_t_m1_i0_one_element_main_block(self) -> None: - sequencer = Sequencer() - - elem = DummySequencingElement(False) - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - sequencer.push(elem, ps, cs) - sequence = sequencer.build() - - self.assertTrue(sequencer.has_finished()) - self.assertIs(elem, sequence[0].elem) - self.assertEqual(ps, elem.parameters) - self.assertEqual(cs, elem.conditions) - self.assertEqual(1, elem.requires_stop_call_counter) - self.assertEqual(1, elem.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[1]) - self.assertEqual(2, len(sequence)) - - def test_build_path_o2_m1_i2_tf_m1_i1_f_two_elements_main_block_last_requires_stop(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - - elem1 = DummySequencingElement(False) - elem2 = DummySequencingElement(True) - sequencer.push(elem2, ps, cs) - sequencer.push(elem1, ps, cs) - - sequence = sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertIs(elem1, sequence[0].elem) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(2, elem2.requires_stop_call_counter) - self.assertEqual(0, elem2.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[1]) - self.assertEqual(2, len(sequence)) - - def test_build_path_o2_m1_i2_tt_m1_i0_two_elements_main_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - - elem1 = DummySequencingElement(False) - elem2 = DummySequencingElement(False) - sequencer.push(elem2, ps, cs) - sequencer.push(elem1, ps, cs) - - sequence = sequencer.build() - - self.assertTrue(sequencer.has_finished()) - self.assertIs(elem1, sequence[0].elem) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertIs(elem2, sequence[1].elem) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(1, elem2.requires_stop_call_counter) - self.assertEqual(1, elem2.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[2]) - self.assertEqual(3, len(sequence)) - - def test_build_path_o2_m1_i1_t_m2_i0_i1_f_one_element_main_block_adds_one_element_requires_stop_new_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - - new_block = InstructionBlock() - new_elem = DummySequencingElement(True) - - elem = DummySequencingElement(False, (new_block, [new_elem])) - sequencer.push(elem, ps, cs) - - sequence = sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertIs(elem, sequence[0].elem) - self.assertEqual(ps, elem.parameters) - self.assertEqual(cs, elem.conditions) - self.assertEqual(1, elem.requires_stop_call_counter) - self.assertEqual(1, elem.build_call_counter) - self.assertEqual(ps, new_elem.parameters) - self.assertEqual(cs, new_elem.conditions) - self.assertEqual(1, new_elem.requires_stop_call_counter) - self.assertEqual(0, new_elem.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[1]) - self.assertEqual(2, len(sequence)) - - def test_build_path_o2_m1_i2_tf_m2_i1_f_i1_f_two_elements_main_block_last_requires_stop_add_one_element_requires_stop_new_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - - new_block = InstructionBlock() - new_elem = DummySequencingElement(True) - - elem1 = DummySequencingElement(False, (new_block, [new_elem])) - elem2 = DummySequencingElement(True) - sequencer.push(elem2, ps, cs) - sequencer.push(elem1, ps, cs) - - instr, stop = sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertIs(elem1, instr.elem) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(2, elem2.requires_stop_call_counter) - self.assertEqual(0, elem2.build_call_counter) - self.assertEqual(ps, new_elem.parameters) - self.assertEqual(cs, new_elem.conditions) - self.assertEqual(1, new_elem.requires_stop_call_counter) - self.assertEqual(0, new_elem.build_call_counter) - self.assertEqual(STOPInstruction(), stop) - - def test_build_path_o2_m2_i0_i1_t_m2_i0_i0_one_element_custom_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo': 'bar'} - cm = {'A': 'B'} - - target_block = InstructionBlock() - elem = DummySequencingElement(False) - sequencer.push(elem, ps, cs, window_mapping=wm, channel_mapping=cm, target_block=target_block) - - sequencer.build() - - self.assertTrue(sequencer.has_finished()) - self.assertIs(target_block, elem.target_block) - self.assertEqual(ps, elem.parameters) - self.assertEqual(cs, elem.conditions) - self.assertEqual(wm, elem.window_mapping) - self.assertEqual(cm, elem.channel_mapping) - self.assertEqual(1, elem.requires_stop_call_counter) - self.assertEqual(1, elem.build_call_counter) - - # which element requires stop is considered a mirror configuration and only tested for this example - def test_build_path_o2_m2_i1_f_i1_t_m2_i1_f_i0_one_element_custom_block_one_element_requires_stop_main_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - - elem_main = DummySequencingElement(True) - sequencer.push(elem_main, ps, cs) - - target_block = InstructionBlock() - elem_cstm = DummySequencingElement(False) - sequencer.push(elem_cstm, ps, cs, target_block=target_block) - - sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertEqual(ps, elem_main.parameters) - self.assertEqual(cs, elem_main.conditions) - self.assertEqual(2, elem_main.requires_stop_call_counter) - self.assertEqual(0, elem_main.build_call_counter) - self.assertIs(target_block, elem_cstm.target_block) - self.assertEqual(ps, elem_cstm.parameters) - self.assertEqual(cs, elem_cstm.conditions) - self.assertEqual(1, elem_cstm.requires_stop_call_counter) - self.assertEqual(1, elem_cstm.build_call_counter) - - def test_build_path_o2_m2_i1_t_i1_t_m2_i0_i0_one_element_custom_block_one_element_main_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - - elem_main = DummySequencingElement(False) - sequencer.push(elem_main, ps, cs) - - target_block = InstructionBlock() - elem_cstm = DummySequencingElement(False) - sequencer.push(elem_cstm, ps, cs, target_block=target_block) - - sequence = sequencer.build() - - self.assertTrue(sequencer.has_finished()) - self.assertIs(elem_main, sequence[0].elem) - self.assertEqual(ps, elem_main.parameters) - self.assertEqual(cs, elem_main.conditions) - self.assertEqual(1, elem_main.requires_stop_call_counter) - self.assertEqual(1, elem_main.build_call_counter) - self.assertIs(target_block, elem_cstm.target_block) - self.assertEqual(ps, elem_cstm.parameters) - self.assertEqual(cs, elem_cstm.conditions) - self.assertEqual(1, elem_cstm.requires_stop_call_counter) - self.assertEqual(1, elem_cstm.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[1]) - self.assertEqual(2, len(sequence)) - - def test_build_path_o2_m2_i0_i2_tf_m2_i0_i1_f_two_elements_custom_block_last_requires_stop(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo': 'bar'} - - target_block = InstructionBlock() - elem2 = DummySequencingElement(True) - sequencer.push(elem2, ps, cs, window_mapping=wm, target_block=target_block) - - elem1 = DummySequencingElement(False) - sequencer.push(elem1, ps, cs, window_mapping=wm, target_block=target_block) - - sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertIs(target_block, elem1.target_block) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(2, elem2.requires_stop_call_counter) - self.assertEqual(0, elem2.build_call_counter) - - def test_build_path_o2_m2_i0_i2_tt_m2_i0_i0_two_elements_custom_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo': 'bar'} - - target_block = InstructionBlock() - elem2 = DummySequencingElement(False) - sequencer.push(elem2, ps, cs, window_mapping=wm, target_block=target_block) - - elem1 = DummySequencingElement(False) - sequencer.push(elem1, ps, cs, window_mapping=wm, target_block=target_block) - - sequencer.build() - - self.assertTrue(sequencer.has_finished()) - self.assertIs(target_block, elem1.target_block) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertIs(target_block, elem2.target_block) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(1, elem2.requires_stop_call_counter) - self.assertEqual(1, elem2.build_call_counter) - - def test_build_path_o2_m2_i1_f_i2_tf_m2_i1_f_i1_f_two_elements_custom_block_last_requires_stop_one_element_requires_stop_main_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo': 'bar'} - - target_block = InstructionBlock() - elem2 = DummySequencingElement(True) - sequencer.push(elem2, ps, cs, window_mapping=wm, target_block=target_block) - - elem1 = DummySequencingElement(False) - sequencer.push(elem1, ps, cs, window_mapping=wm, target_block=target_block) - - elem_main = DummySequencingElement(True) - sequencer.push(elem_main, ps, cs) - - sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertIs(target_block, elem1.target_block) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(2, elem2.requires_stop_call_counter) - self.assertEqual(0, elem2.build_call_counter) - self.assertEqual(ps, elem_main.parameters) - self.assertEqual(cs, elem_main.conditions) - self.assertEqual(2, elem_main.requires_stop_call_counter) - self.assertEqual(0, elem_main.build_call_counter) - - def test_build_path_o2_m2_i1_t_i2_tf_m2_i0_i1_f_two_elements_custom_block_last_requires_stop_one_element_main_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo': 'bar'} - - target_block = InstructionBlock() - elem2 = DummySequencingElement(True) - sequencer.push(elem2, ps, cs, window_mapping=wm, target_block=target_block) - - elem1 = DummySequencingElement(False) - sequencer.push(elem1, ps, cs, window_mapping=wm, target_block=target_block) - - elem_main = DummySequencingElement(False) - sequencer.push(elem_main, ps, cs) - - sequence = sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertIs(target_block, elem1.target_block) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(2, elem2.requires_stop_call_counter) - self.assertEqual(0, elem2.build_call_counter) - self.assertIs(elem_main, sequence[0].elem) - self.assertEqual(ps, elem_main.parameters) - self.assertEqual(cs, elem_main.conditions) - self.assertEqual(1, elem_main.requires_stop_call_counter) - self.assertEqual(1, elem_main.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[1]) - self.assertEqual(2, len(sequence)) - - def test_build_path_o2_m2_i1_t_i2_tt_m2_i0_i0_two_elements_custom_block_one_element_main_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo': 'bar'} - - target_block = InstructionBlock() - elem2 = DummySequencingElement(False) - sequencer.push(elem2, ps, cs, window_mapping=wm, target_block=target_block) - - elem1 = DummySequencingElement(False) - sequencer.push(elem1, ps, cs, window_mapping=wm, target_block=target_block) - - elem_main = DummySequencingElement(False) - sequencer.push(elem_main, ps, cs) - - sequence = sequencer.build() - - self.assertTrue(sequencer.has_finished()) - self.assertIs(target_block, elem1.target_block) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertIs(target_block, elem2.target_block) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(1, elem2.requires_stop_call_counter) - self.assertEqual(1, elem2.build_call_counter) - self.assertIs(elem_main, sequence[0].elem) - self.assertEqual(ps, elem_main.parameters) - self.assertEqual(cs, elem_main.conditions) - self.assertEqual(1, elem_main.requires_stop_call_counter) - self.assertEqual(1, elem_main.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[1]) - self.assertEqual(2, len(sequence)) - - def test_build_path_o2_m2_i2_tf_t_i2_tf_m2_i1_f_i1_f_two_elements_custom_block_last_requires_stop_two_element_main_block_last_requires_stop(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo': 'bar'} - - target_block = InstructionBlock() - elem2 = DummySequencingElement(True) - sequencer.push(elem2, ps, cs, window_mapping=wm, target_block=target_block) - - elem1 = DummySequencingElement(False) - sequencer.push(elem1, ps, cs, window_mapping=wm, target_block=target_block) - - elem_main2 = DummySequencingElement(True) - sequencer.push(elem_main2, ps, cs) - - elem_main1 = DummySequencingElement(False) - sequencer.push(elem_main1, ps, cs) - - sequence = sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertIs(target_block, elem1.target_block) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(2, elem2.requires_stop_call_counter) - self.assertEqual(0, elem2.build_call_counter) - self.assertIs(elem_main1, sequence[0].elem) - self.assertEqual(ps, elem_main1.parameters) - self.assertEqual(cs, elem_main1.conditions) - self.assertEqual(1, elem_main1.requires_stop_call_counter) - self.assertEqual(1, elem_main1.build_call_counter) - self.assertEqual(ps, elem_main2.parameters) - self.assertEqual(cs, elem_main2.conditions) - self.assertEqual(2, elem_main2.requires_stop_call_counter) - self.assertEqual(0, elem_main2.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[1]) - self.assertEqual(2, len(sequence)) - - # which block contains the element that requires a stop is considered a mirror configuration and only tested for this example - def test_build_path_o2_m2_i2_tt_t_i2_tf_m2_i0_i1_f_two_elements_custom_block_last_requires_stop_two_element_main_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo': 'bar'} - - target_block = InstructionBlock() - elem2 = DummySequencingElement(True) - sequencer.push(elem2, ps, cs, window_mapping=wm, target_block=target_block) - - elem1 = DummySequencingElement(False) - sequencer.push(elem1, ps, cs, window_mapping=wm, target_block=target_block) - - elem_main2 = DummySequencingElement(False) - sequencer.push(elem_main2, ps, cs) - - elem_main1 = DummySequencingElement(False) - sequencer.push(elem_main1, ps, cs) - - sequence = sequencer.build() - - self.assertFalse(sequencer.has_finished()) - self.assertIs(target_block, elem1.target_block) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(2, elem2.requires_stop_call_counter) - self.assertEqual(0, elem2.build_call_counter) - self.assertIs(elem_main1, sequence[0].elem) - self.assertEqual(ps, elem_main1.parameters) - self.assertEqual(cs, elem_main1.conditions) - self.assertEqual(1, elem_main1.requires_stop_call_counter) - self.assertEqual(1, elem_main1.build_call_counter) - self.assertIs(elem_main2, sequence[1].elem) - self.assertEqual(ps, elem_main2.parameters) - self.assertEqual(cs, elem_main2.conditions) - self.assertEqual(1, elem_main2.requires_stop_call_counter) - self.assertEqual(1, elem_main2.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[2]) - self.assertEqual(3, len(sequence)) - - def test_build_path_o2_m2_i2_tt_t_i2_tt_m2_i0_i0_two_elements_custom_block_two_element_main_block(self) -> None: - sequencer = Sequencer() - - ps = {'foo': ConstantParameter(1), 'bar': ConstantParameter(7.3)} - cs = {'foo': DummyCondition()} - wm = {'foo': 'bar'} - - target_block = InstructionBlock() - elem2 = DummySequencingElement(False) - sequencer.push(elem2, ps, cs, window_mapping=wm, target_block=target_block) - - elem1 = DummySequencingElement(False) - sequencer.push(elem1, ps, cs, window_mapping=wm, target_block=target_block) - - elem_main2 = DummySequencingElement(False) - sequencer.push(elem_main2, ps, cs) - - elem_main1 = DummySequencingElement(False) - sequencer.push(elem_main1, ps, cs) - - sequence = sequencer.build() - - self.assertTrue(sequencer.has_finished()) - self.assertIs(target_block, elem1.target_block) - self.assertEqual(ps, elem1.parameters) - self.assertEqual(cs, elem1.conditions) - self.assertEqual(1, elem1.requires_stop_call_counter) - self.assertEqual(1, elem1.build_call_counter) - self.assertIs(target_block, elem2.target_block) - self.assertEqual(ps, elem2.parameters) - self.assertEqual(cs, elem2.conditions) - self.assertEqual(1, elem2.requires_stop_call_counter) - self.assertEqual(1, elem2.build_call_counter) - self.assertIs(elem_main1, sequence[0].elem) - self.assertEqual(ps, elem_main1.parameters) - self.assertEqual(cs, elem_main1.conditions) - self.assertEqual(1, elem_main1.requires_stop_call_counter) - self.assertEqual(1, elem_main1.build_call_counter) - self.assertIs(elem_main2, sequence[1].elem) - self.assertEqual(ps, elem_main2.parameters) - self.assertEqual(cs, elem_main2.conditions) - self.assertEqual(1, elem_main2.requires_stop_call_counter) - self.assertEqual(1, elem_main2.build_call_counter) - self.assertEqual(STOPInstruction(), sequence[2]) - self.assertEqual(3, len(sequence)) - - # path 1_2_3_4_5_6_7_8_5_3_9 can never occur: 8 sets shall_continue = True, so 3 cannot evaluate to False - -if __name__ == "__main__": - unittest.main(verbosity=2) \ No newline at end of file diff --git a/tests/pulses/table_pulse_template_tests.py b/tests/pulses/table_pulse_template_tests.py index 6c790af3a..8fc7fd49c 100644 --- a/tests/pulses/table_pulse_template_tests.py +++ b/tests/pulses/table_pulse_template_tests.py @@ -10,8 +10,7 @@ from qupulse.pulses.interpolation import HoldInterpolationStrategy, LinearInterpolationStrategy, JumpInterpolationStrategy from qupulse.pulses.multi_channel_pulse_template import MultiChannelWaveform -from tests.pulses.sequencing_dummies import DummyInterpolationStrategy, DummyParameter, DummyCondition,\ - DummyPulseTemplate +from tests.pulses.sequencing_dummies import DummyInterpolationStrategy, DummyParameter, DummyPulseTemplate from tests.serialization_dummies import DummySerializer, DummyStorageBackend from tests.pulses.measurement_tests import ParameterConstrainerTest, MeasurementDefinerTest from tests.serialization_tests import SerializableTests @@ -171,9 +170,6 @@ def test_external_constraints(self): table.build_waveform(parameters=dict(v=1., w=2, t=0.1, x=1.2, y=1, h=2), channel_mapping={0: 0, 1: 1}) - def test_is_interruptable(self) -> None: - self.assertFalse(TablePulseTemplate({0: [(1, 1)]}).is_interruptable) - def test_get_entries_instantiated_one_entry_float_float(self) -> None: table = TablePulseTemplate({0: [(0, 2)]}) instantiated_entries = table.get_entries_instantiated(dict())[0] @@ -634,26 +630,6 @@ def test_build_waveform_empty(self) -> None: table = TablePulseTemplate(dict(a=[('t', 0)])) self.assertIsNone(table.build_waveform(dict(t=0), dict(a='a'))) - def test_requires_stop_missing_param(self) -> None: - table = TablePulseTemplate({0: [('foo', 'v')]}) - with self.assertRaises(ParameterNotProvidedException): - table.requires_stop({'foo': DummyParameter(0, False)}, {}) - - def test_requires_stop(self) -> None: - table = TablePulseTemplate({0: [('foo', 'v'), - ('bar', 0)]}) - test_sets = [(False, {'foo': DummyParameter(0, False), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, False)}, {'foo': DummyCondition(False)}), - (False, {'foo': DummyParameter(0, False), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, False)}, {'foo': DummyCondition(True)}), - (True, {'foo': DummyParameter(0, True), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, False)}, {'foo': DummyCondition(False)}), - (True, {'foo': DummyParameter(0, True), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, False)}, {'foo': DummyCondition(True)}), - (True, {'foo': DummyParameter(0, False), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, True)}, {'foo': DummyCondition(False)}), - (True, {'foo': DummyParameter(0, False), 'bar': DummyParameter(0, False), 'v': DummyParameter(0, True)}, {'foo': DummyCondition(True)}), - (True, {'foo': DummyParameter(0, True), 'bar': DummyParameter(0, True), 'v': DummyParameter(0, True)}, {'foo': DummyCondition(False)}), - (True, {'foo': DummyParameter(0, True), 'bar': DummyParameter(0, True), 'v': DummyParameter(0, True)}, {'foo': DummyCondition(True)})] - for expected_result, parameter_set, condition_set in test_sets: - self.assertEqual(expected_result, table.requires_stop(parameter_set, condition_set)) - - class TablePulseConcatenationTests(unittest.TestCase): def test_simple_concatenation(self): diff --git a/tests/pulses/table_sequence_sequencer_intergration_tests.py b/tests/pulses/table_sequence_sequencer_intergration_tests.py deleted file mode 100644 index ec197b687..000000000 --- a/tests/pulses/table_sequence_sequencer_intergration_tests.py +++ /dev/null @@ -1,83 +0,0 @@ -import unittest - -from qupulse.pulses.multi_channel_pulse_template import MappingPulseTemplate -from qupulse.pulses.table_pulse_template import TablePulseTemplate -from qupulse.pulses.sequence_pulse_template import SequencePulseTemplate -from qupulse.pulses.parameters import ParameterNotProvidedException -from qupulse.pulses.sequencing import Sequencer -from qupulse._program.instructions import EXECInstruction, AbstractInstructionBlock, MEASInstruction - -from tests.pulses.sequencing_dummies import DummyParameter, DummyNoValueParameter - - -class TableSequenceSequencerIntegrationTests(unittest.TestCase): - - def test_table_sequence_sequencer_integration(self) -> None: - t1 = TablePulseTemplate(entries={'default': [(2, 'foo'), - (5, 0)]}, - measurements=[('foo', 2, 2)]) - - t2 = TablePulseTemplate(entries={'default': [(4, 0), - (4.5, 'bar', 'linear'), - (5, 0)]}, - measurements=[('foo', 4, 1)]) - - seqt = SequencePulseTemplate(MappingPulseTemplate(t1, measurement_mapping={'foo': 'bar'}), - MappingPulseTemplate(t2, parameter_mapping={'bar': '2 * hugo'})) - - with self.assertRaises(ParameterNotProvidedException): - t1.requires_stop(dict(), dict()) - with self.assertRaises(ParameterNotProvidedException): - t2.requires_stop(dict(), dict()) - self.assertFalse(seqt.requires_stop({'foo': DummyParameter(), 'hugo': DummyParameter()}, {})) - - foo = DummyNoValueParameter() - bar = DummyNoValueParameter() - sequencer = Sequencer() - sequencer.push(seqt, {'foo': foo, 'hugo': bar}, - window_mapping=dict(bar='my', foo='thy'), - channel_mapping={'default': 'A'}) - instructions = sequencer.build() - self.assertFalse(sequencer.has_finished()) - self.assertEqual(1, len(instructions)) - - # stop after first TablePT - foo = DummyParameter(value=1.1) - bar = DummyNoValueParameter() - sequencer = Sequencer() - sequencer.push(seqt, {'foo': foo, 'hugo': bar}, - window_mapping=dict(bar='my', foo='thy'), - channel_mapping={'default': 'A'}) - block = sequencer.build() - instructions = block.instructions - self.assertFalse(sequencer.has_finished()) - self.assertIsInstance(block, AbstractInstructionBlock) - self.assertEqual(2, len(instructions)) - self.assertEqual(instructions[0], MEASInstruction([('my', 2, 2)])) - self.assertIsInstance(instructions[1], EXECInstruction) - - # stop before first TablePT - foo = DummyParameter(value=1.1) - bar = DummyNoValueParameter() - sequencer = Sequencer() - sequencer.push(seqt, {'foo': bar, 'hugo': foo}, - window_mapping=dict(bar='my', foo='thy'), - channel_mapping={'default': 'A'}) - instructions = sequencer.build() - self.assertFalse(sequencer.has_finished()) - self.assertEqual(1, len(instructions)) - - foo = DummyParameter(value=1.1) - bar = DummyParameter(value=-0.2) - sequencer = Sequencer() - sequencer.push(seqt, {'foo': foo, 'hugo': bar}, - window_mapping=dict(bar='my', foo='thy'), - channel_mapping={'default': 'A'}) - instructions = sequencer.build() - self.assertTrue(sequencer.has_finished()) - self.assertEqual(4, len(instructions.instructions)) - - self.assertEqual(instructions[0], MEASInstruction([('my', 2, 2)])) - self.assertIsInstance(instructions[1], EXECInstruction) - self.assertEqual(instructions[2], MEASInstruction([('thy', 4, 1)])) - self.assertIsInstance(instructions[3], EXECInstruction) diff --git a/tests/serialization_tests.py b/tests/serialization_tests.py index 8746908e0..46a4b94b1 100644 --- a/tests/serialization_tests.py +++ b/tests/serialization_tests.py @@ -250,8 +250,6 @@ def class_to_test(self): def make_kwargs(self): return { - 'requires_stop': True, - 'is_interruptable': True, 'parameter_names': {'foo', 'bar'}, 'defined_channels': {'default', 'not_default'}, 'duration': ExpressionScalar('17.3*foo+bar'),