diff --git a/.travis.yml b/.travis.yml index 96e8779da..48802f5bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python python: - - "3.3" - "3.4" - "3.5" diff --git a/doc/source/examples/00SimpleTablePulse.ipynb b/doc/source/examples/00SimpleTablePulse.ipynb index 0c0857efe..6e8c5eeec 100644 --- a/doc/source/examples/00SimpleTablePulse.ipynb +++ b/doc/source/examples/00SimpleTablePulse.ipynb @@ -894,7 +894,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'va', 'ta', 'tb', 'tend', 'vb'}\n" + "{'tb', 'ta', 'va', 'vb', 'tend'}\n" ] } ], @@ -2503,6 +2503,15 @@ " 'tend': 8}\n", "plot(param_template, parameters, sample_rate=100)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -2521,7 +2530,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.4" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/01SequencePulse.ipynb b/doc/source/examples/01SequencePulse.ipynb index af791e970..ec7a66fb4 100644 --- a/doc/source/examples/01SequencePulse.ipynb +++ b/doc/source/examples/01SequencePulse.ipynb @@ -82,7 +82,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "frozenset({'tend', 'va', 'ta', 'tc', 'vb', 'tb', 'td'})\n" + "frozenset({'tend', 'ta', 'tc', 'vb', 'va', 'td', 'tb'})\n" ] } ], @@ -897,6 +897,15 @@ " 'tend': 6}\n", "plot(sequence, parameters, sample_rate=100)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -915,7 +924,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.4" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/02FunctionPulse.ipynb b/doc/source/examples/02FunctionPulse.ipynb index 0b9e8b38c..69f080476 100644 --- a/doc/source/examples/02FunctionPulse.ipynb +++ b/doc/source/examples/02FunctionPulse.ipynb @@ -1608,6 +1608,15 @@ "\n", "plot(param_template, {'lambda': 4, 'phi': 8, 'duration': 4*3.1415}, sample_rate=100)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -1626,7 +1635,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.4" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/03Serialization.ipynb b/doc/source/examples/03Serialization.ipynb index 95490399d..3d24788dd 100644 --- a/doc/source/examples/03Serialization.ipynb +++ b/doc/source/examples/03Serialization.ipynb @@ -927,6 +927,15 @@ "from qctoolkit.serialization import CachingBackend\n", "cached_serializer = Serializer(CachingBackend(FilesystemBackend(\"./serialized_pulses\")))" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -945,7 +954,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.4" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/04Sequencing.ipynb b/doc/source/examples/04Sequencing.ipynb index c7c152cf9..73c73a47b 100644 --- a/doc/source/examples/04Sequencing.ipynb +++ b/doc/source/examples/04Sequencing.ipynb @@ -845,7 +845,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , ]\n" + "[, , ]\n" ] } ], @@ -855,8 +855,8 @@ "sequencer = Sequencer()\n", "\n", "sequencer.push(sequence_template, {'lambda': 4, 'phi': 8, 'duration': 4*3.1415, 'ta': 1, 'va': 2})\n", - "instruction_sequence = sequencer.build()\n", - "print(instruction_sequence)" + "instruction_block = sequencer.build()\n", + "print([instruction for instruction in instruction_block])" ] }, { @@ -884,6 +884,15 @@ "The latter two will only be generated when hardware-based conditional branching is used in `LoopPulseTemplate` and `BranchPulseTemplate`. This was the main motivator for the usage of such an instruction sequence rather than just compling one large waveform.\n", "Using only the `PulseTemplate`s discussed so far, only `EXECInstruction`s and a final `STOPInstruction` will be output by the `Sequencer`." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -902,7 +911,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.4" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/05Parameters.ipynb b/doc/source/examples/05Parameters.ipynb index ad59f99ba..6d56d0dbf 100644 --- a/doc/source/examples/05Parameters.ipynb +++ b/doc/source/examples/05Parameters.ipynb @@ -924,13 +924,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , ]\n" + "[, , ]\n" ] } ], "source": [ - "first_sequence = sequencer.build()\n", - "print(first_sequence)" + "first_block = sequencer.build()\n", + "print([instruction for instruction in first_block])" ] }, { @@ -951,14 +951,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , , , ]\n" + "[, , , , ]\n" ] } ], "source": [ "measurement_manager.is_available = True\n", - "second_sequence = sequencer.build()\n", - "print(second_sequence)" + "second_block = sequencer.build()\n", + "print([instruction for instruction in second_block])" ] }, { @@ -968,6 +968,15 @@ "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)).*" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -986,7 +995,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.4" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/06ConditionalExecution.ipynb b/doc/source/examples/06ConditionalExecution.ipynb index 7b5e394ba..ef76672ef 100644 --- a/doc/source/examples/06ConditionalExecution.ipynb +++ b/doc/source/examples/06ConditionalExecution.ipynb @@ -62,7 +62,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , , , , ]\n" + "[, , , , , ]\n" ] } ], @@ -76,7 +76,7 @@ "parameters = {}\n", "s.push(loop_template, parameters, conditions)\n", "instructions = s.build()\n", - "print(instructions)" + "print([instruction for instruction in instructions])" ] }, { @@ -107,7 +107,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , , ]\n" + "[, ]\n" ] } ], @@ -124,7 +124,7 @@ "parameters = {}\n", "s.push(loop_template, parameters, conditions)\n", "instructions = s.build()\n", - "print(instructions)" + "print([instruction for instruction in instructions])" ] }, { @@ -133,6 +133,15 @@ "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." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -151,7 +160,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.4" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/07PulseControl.ipynb b/doc/source/examples/07PulseControl.ipynb index 13e46b3c1..02c779216 100644 --- a/doc/source/examples/07PulseControl.ipynb +++ b/doc/source/examples/07PulseControl.ipynb @@ -33,7 +33,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.3" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/08RealWorldCase.ipynb b/doc/source/examples/08RealWorldCase.ipynb index 5002cbafb..2ce8a5d93 100644 --- a/doc/source/examples/08RealWorldCase.ipynb +++ b/doc/source/examples/08RealWorldCase.ipynb @@ -907,7 +907,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1683,7 +1683,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2459,7 +2459,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2595,7 +2595,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let us plot $S_1'$ to see what whether we've accomplished our goal:" + "Let us plot $S_1'$ to see whether we've accomplished our goal:" ] }, { @@ -3372,7 +3372,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3395,7 +3395,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "collapsed": false }, @@ -3411,6 +3411,15 @@ " \n", "scanline = SequencePulseTemplate(subtemplates, all_epsilons)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -3429,7 +3438,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.4" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/09DetailedSequencingWalkthrough.ipynb b/doc/source/examples/09DetailedSequencingWalkthrough.ipynb index bc295e4de..5d364fe21 100644 --- a/doc/source/examples/09DetailedSequencingWalkthrough.ipynb +++ b/doc/source/examples/09DetailedSequencingWalkthrough.ipynb @@ -61,7 +61,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[(, {'foo': }, {'rcon': })]\n" + "[(, {'foo': }, {'rcon': })]\n" ] } ], @@ -114,7 +114,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[(, {'foo': }, {'rcon': }), (, {'foo': }, {'rcon': })]\n" + "[(, {'foo': }, {'rcon': }), (, {'foo': }, {'rcon': })]\n" ] } ], @@ -191,7 +191,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[]\n" + "[]\n" ] } ], @@ -217,7 +217,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[(, {'foo': }, {'rcon': })]\n" + "[(, {'foo': }, {'rcon': })]\n" ] } ], @@ -243,7 +243,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[(, {'foo': }, {'rcon': }), (, {'foo': }, {'rcon': })]\n" + "[(, {'foo': }, {'rcon': }), (, {'foo': }, {'rcon': })]\n" ] } ], @@ -276,7 +276,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[(, {'foo': }, {'rcon': })]\n" + "[(, {'foo': }, {'rcon': })]\n" ] } ], @@ -309,7 +309,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, ]\n" + "[, ]\n" ] } ], @@ -356,7 +356,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Note that we have not yet obtained an `InstructionSequence` but only constructed the main `InstructionBlock` object. We will conclude the sequencing by converting the main block into the desired sequence:" + "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`:" ] }, { @@ -370,20 +370,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , ]\n" + "[, , ]\n" ] } ], "source": [ - "instruction_sequence = main_block.compile_sequence()\n", - "print(instruction_sequence)" + "from qctoolkit.pulses import ImmutableInstructionBlock\n", + "return_block = ImmutableInstructionBlock(main_block)\n", + "print([instruction for instruction in return_block])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In this case, this is a trivial task, as it simply takes both instructions contains in the main block and adds a `STOPInstruction` to generate the sequence. Now we are done." + "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." ] }, { @@ -423,7 +424,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , ]\n" + "[, , ]\n" ] } ], @@ -432,8 +433,8 @@ "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", - "instruction_sequence = s.build()\n", - "print(instruction_sequence)" + "instruction_block = s.build()\n", + "print([instruction for instruction in instruction_block])" ] }, { @@ -519,7 +520,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[(, {'foo': }, {'bcon': , 'lcon': })]\n" + "[(, {'foo': }, {'bcon': , 'lcon': })]\n" ] } ], @@ -580,7 +581,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[]\n" + "[]\n" ] } ], @@ -619,7 +620,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[(, {'foo': }, {'bcon': , 'lcon': })]\n" + "[(, {'foo': }, {'bcon': , 'lcon': })]\n" ] } ], @@ -633,7 +634,7 @@ "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:" + "`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:" ] }, { @@ -680,7 +681,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, ]\n" + "[, ]\n" ] } ], @@ -771,13 +772,9 @@ "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. In an illustration, the blocks look like this:\n", + "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", - "![Interconnected instruction block](img/walkthrough2_04.png)\n", - "\n", - "Note that the returning unconditional gotos are not listed explicitely in the outputs above but always implicitely defined.\n", - "\n", - "In the final step of the sequencing process, these blocks are now converted into a single sequence of instructions by embedding each blocks instructions into the instructions of the block jumping into it and then adapting the jump pointers. The final sequence is as follows:" + "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." ] }, { @@ -791,22 +788,40 @@ "name": "stdout", "output_type": "stream", "text": [ - "[, , , , , , , , ]\n" + "Main Block:\n", + "[, ]\n", + "\n", + "Loop Body Block:\n", + "[, , ]\n", + "\n", + "If Branch Block:\n", + "[, ]\n", + "\n", + "Else Branch Block:\n", + "[, ]\n" ] } ], "source": [ - "instruction_sequence = main_block.compile_sequence()\n", - "print(instruction_sequence)" + "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": [ - "![Resulting instruction sequence](img/walkthrough2_05.png)\n", + " In an illustration, the blocks and the jumps between them look like this:\n", "\n", - "This instruction sequence indeed represents our desired behavior." + "![Interconnected instruction block](img/walkthrough2_04.png)\n" ] }, { @@ -827,6 +842,15 @@ "source": [ "print(s.has_finished()) # really done?" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -845,7 +869,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.4.4" + "version": "3.5.1" } }, "nbformat": 4, diff --git a/doc/source/examples/serialized_pulses/sequence_embedded b/doc/source/examples/serialized_pulses/sequence_embedded index d0d71498b..fa62fd203 100644 --- a/doc/source/examples/serialized_pulses/sequence_embedded +++ b/doc/source/examples/serialized_pulses/sequence_embedded @@ -4,11 +4,26 @@ "subtemplates": [ { "mappings": { - "ta": "1", - "tb": "2", - "tend": "5", - "va": "5", - "vb": "0" + "ta": { + "expression": "1", + "type": "qctoolkit.expressions.Expression" + }, + "tb": { + "expression": "2", + "type": "qctoolkit.expressions.Expression" + }, + "tend": { + "expression": "5", + "type": "qctoolkit.expressions.Expression" + }, + "va": { + "expression": "5", + "type": "qctoolkit.expressions.Expression" + }, + "vb": { + "expression": "0", + "type": "qctoolkit.expressions.Expression" + } }, "template": { "entries": [ diff --git a/doc/source/examples/serialized_pulses/sequence_referenced b/doc/source/examples/serialized_pulses/sequence_referenced index 7d5e42fb9..da025f3ea 100644 --- a/doc/source/examples/serialized_pulses/sequence_referenced +++ b/doc/source/examples/serialized_pulses/sequence_referenced @@ -4,11 +4,26 @@ "subtemplates": [ { "mappings": { - "ta": "1", - "tb": "2", - "tend": "5", - "va": "5", - "vb": "0" + "ta": { + "expression": "1", + "type": "qctoolkit.expressions.Expression" + }, + "tb": { + "expression": "2", + "type": "qctoolkit.expressions.Expression" + }, + "tend": { + "expression": "5", + "type": "qctoolkit.expressions.Expression" + }, + "va": { + "expression": "5", + "type": "qctoolkit.expressions.Expression" + }, + "vb": { + "expression": "0", + "type": "qctoolkit.expressions.Expression" + } }, "template": "table_template" } diff --git a/qctoolkit/pulses/conditions.py b/qctoolkit/pulses/conditions.py index 55a2cb39e..839ff95ec 100644 --- a/qctoolkit/pulses/conditions.py +++ b/qctoolkit/pulses/conditions.py @@ -123,8 +123,9 @@ def build_sequence_loop(self, parameters: Dict[str, Parameter], conditions: Dict[str, Condition], instruction_block: InstructionBlock) -> None: - body_block = instruction_block.create_embedded_block() - body_block.return_ip = InstructionPointer(instruction_block, len(body_block)) + 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, body_block) @@ -137,8 +138,8 @@ def build_sequence_branch(self, parameters: Dict[str, Parameter], conditions: Dict[str, Condition], instruction_block: InstructionBlock) -> None: - if_block = instruction_block.create_embedded_block() - else_block = instruction_block.create_embedded_block() + if_block = InstructionBlock() + else_block = InstructionBlock() instruction_block.add_instruction_cjmp(self.__trigger, if_block) sequencer.push(if_branch, parameters, conditions, if_block) @@ -146,7 +147,8 @@ def build_sequence_branch(self, instruction_block.add_instruction_goto(else_block) sequencer.push(else_branch, parameters, conditions, else_block) - if_block.return_ip = InstructionPointer(instruction_block, len(instruction_block)) + if_block.return_ip = InstructionPointer(instruction_block, + len(instruction_block.instructions)) else_block.return_ip = if_block.return_ip diff --git a/qctoolkit/pulses/instructions.py b/qctoolkit/pulses/instructions.py index 3f1ee2208..47265032a 100644 --- a/qctoolkit/pulses/instructions.py +++ b/qctoolkit/pulses/instructions.py @@ -5,30 +5,27 @@ - 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. - - InstructionBlock: A block of instructions which are not yet embedded in a global sequence. + - 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. - - InstructionBlockNotYetPlacedException - - InstructionBlockAlreadyFinalizedException - - MissingReturnAddressException """ from abc import ABCMeta, abstractmethod, abstractproperty -from typing import List, Any +from typing import List, Any, Dict, Iterable, Optional import numpy from qctoolkit.comparable import Comparable -# TODO lumip: add docstrings to InstructionBlock after issue #116 is resolved - -__all__ = ["Waveform", "Trigger", "InstructionPointer", - "Instruction", "CJMPInstruction", "EXECInstruction", "GOTOInstruction", - "STOPInstruction", "InstructionBlock", "InstructionSequence", - "InstructionBlockNotYetPlacedException", "InstructionBlockAlreadyFinalizedException", - "MissingReturnAddressException" +__all__ = ["Waveform", "Trigger", + "InstructionPointer", "Instruction", "CJMPInstruction", "EXECInstruction", + "GOTOInstruction", "STOPInstruction", "AbstractInstructionBlock", "InstructionBlock", + "ImmutableInstructionBlock", "InstructionSequence" ] @@ -80,30 +77,44 @@ def __str__(self) -> str: class InstructionPointer(Comparable): - """Reference to the location of an InstructionBlock. + """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: 'InstructionBlock', offset: int) -> None: + def __init__(self, block: 'AbstractInstructionBlock', offset: int=0) -> None: + """Create a new InstructionPointer instance. + + Args: + block (AbstractInstructionBlock): The instruction block the referenced instruction + resides in. + offset (int): 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 - - def get_absolute_address(self) -> int: - """Return the absolute offset of the targeted instruction in the final instruction sequence. - """ - return self.block.get_start_address() + self.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 + return id(self.__block), self.__offset def __str__(self) -> str: - try: - return "{}".format(self.get_absolute_address()) - except InstructionBlockNotYetPlacedException: - return "IP:{0}#{1}".format(self.block, self.offset) + return "IP:{0}#{1}".format(self.__block, self.__offset) class Instruction(Comparable, metaclass=ABCMeta): @@ -112,10 +123,6 @@ class Instruction(Comparable, metaclass=ABCMeta): def __init__(self) -> None: super().__init__() - @abstractmethod - def compare_key(self) -> Any: - pass - class CJMPInstruction(Instruction): """A conditional jump hardware instruction. @@ -125,10 +132,18 @@ class CJMPInstruction(Instruction): effect, the execution will continue with the following. """ - def __init__(self, trigger: Trigger, block: 'InstructionBlock', offset: int=0) -> None: + 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 = InstructionPointer(block, offset) + self.target = target @property def compare_key(self) -> Any: @@ -138,6 +153,37 @@ def __str__(self) -> str: return "cjmp to {} on {}".format(self.target, self.trigger) +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. @@ -145,9 +191,15 @@ class GOTOInstruction(Instruction): held by this GOTOInstruction. """ - def __init__(self, block: 'InstructionBlock', offset: int=0) -> None: + 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 = InstructionPointer(block, offset) + self.target = target @property def compare_key(self) -> Any: @@ -161,6 +213,11 @@ class EXECInstruction(Instruction): """An instruction to execute/play back a waveform.""" def __init__(self, waveform: Waveform) -> None: + """Create a new EXECInstruction object. + + Args: + waveform (Waveform): The waveform that will be executed by this instruction. + """ super().__init__() self.waveform = waveform @@ -176,6 +233,7 @@ class STOPInstruction(Instruction): """An instruction which indicates the end of the program.""" def __init__(self) -> None: + """Create a new STOPInstruction object.""" super().__init__() @property @@ -186,115 +244,235 @@ def __str__(self) -> str: return "stop" -class InstructionBlockAlreadyFinalizedException(Exception): - """Indicates that an attempt was made to change an already finalized InstructionBlock.""" - def __str__(self) -> str: - return "An attempt was made to change an already finalized InstructionBlock." - - -class InstructionBlockNotYetPlacedException(Exception): - """Indicates that an attempt was made to obtain the start address of an InstructionBlock that - was not yet placed inside the corresponding outer block.""" - def __str__(self) -> str: - return "An attempt was made to obtain the start address of an InstructionBlock that was " \ - "not yet finally placed inside the corresponding outer block." +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__() + + @abstractproperty + def instructions(self) -> List[Instruction]: + """The instructions contained in this block (excluding a final stop or return goto).""" + + @abstractproperty + 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) -class MissingReturnAddressException(Exception): - """Indicates that an inner InstructionBlock has no return address.""" def __str__(self) -> str: - return "No return address is set!" - - -InstructionSequence = List[Instruction] # pylint: disable=invalid-name,invalid-sequence-index + 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: int) -> Instruction: + if index > len(self.instructions) or index < -(len(self.instructions) + 1): + raise IndexError() + if index < 0: + return self[len(self) + index] + if index < len(self.instructions): + return self.instructions[index] + elif index == len(self.instructions): + if self.return_ip is None: + return STOPInstruction() + else: + return GOTOInstruction(self.return_ip) -class InstructionBlock(Comparable): - - def __init__(self, outer_block: 'InstructionBlock'=None) -> None: + 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.__embedded_blocks = [] # type: List[InstructionBlock] - self.__outer_block = outer_block - self.__offset = None - if self.__outer_block is None: - self.__offset = 0 - self.return_ip = None - self.__compiled_sequence = None # type: InstructionSequence - + + self.__return_ip = None + def add_instruction(self, instruction: Instruction) -> None: - # change to instructions -> invalidate cached compiled sequence - if self.__compiled_sequence is not None: - self.__compiled_sequence = None - for block in self.__embedded_blocks: - block.__offset = 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', offset: int=0) -> None: - self.add_instruction(GOTOInstruction(target_block, offset)) - + + 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', - offset: int=0) -> None: - self.add_instruction(CJMPInstruction(trigger, target_block, offset)) + 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()) - + @property def instructions(self) -> InstructionSequence: return self.__instruction_list.copy() - - def create_embedded_block(self) -> 'InstructionBlock': - block = InstructionBlock(self) - self.__embedded_blocks.append(block) - return block - - def compile_sequence(self) -> InstructionSequence: - # do not recompile if no changes happened - if self.__compiled_sequence is not None: - return self.__compiled_sequence - - # clear old offsets - for block in self.__embedded_blocks: - block.__offset = None - - self.__compiled_sequence = self.__instruction_list.copy() - - if self.__outer_block is None: - self.__compiled_sequence.append(STOPInstruction()) - elif self.return_ip is not None: - self.__compiled_sequence.append( - GOTOInstruction(self.return_ip.block, self.return_ip.offset)) - else: - self.__compiled_sequence = None - raise MissingReturnAddressException() - - for block in self.__embedded_blocks: - block.__offset = len(self.__compiled_sequence) - block_sequence = block.compile_sequence() - self.__compiled_sequence.extend(block_sequence) - - return self.__compiled_sequence - - def get_start_address(self) -> int: - if self.__offset is None: - raise InstructionBlockNotYetPlacedException() - pos = self.__offset - if self.__outer_block is not None: - pos += self.__outer_block.get_start_address() - return pos - - def __len__(self) -> int: - return len(self.__instruction_list) @property - def compare_key(self) -> Any: - return id(self) - - def __str__(self) -> str: - return str(hash(self)) + def return_ip(self) -> InstructionPointer: + return self.__return_ip + + @return_ip.setter + def return_ip(self, value: InstructionPointer) -> None: + self.__return_ip = value + + +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.__instruction_list = [] + 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 + for instruction in block.instructions: + immutable_instruction = instruction + if isinstance(instruction, (GOTOInstruction, CJMPInstruction, REPJInstruction)): + target_block = instruction.target.block + immutable_target_block = ImmutableInstructionBlock(target_block, context) + if isinstance(instruction, GOTOInstruction): + immutable_instruction = GOTOInstruction( + InstructionPointer(immutable_target_block, instruction.target.offset) + ) + elif isinstance(instruction, REPJInstruction): + immutable_instruction = REPJInstruction( + instruction.count, + InstructionPointer(immutable_target_block, instruction.target.offset) + ) + else: + immutable_instruction = CJMPInstruction( + instruction.trigger, + InstructionPointer(immutable_target_block, instruction.target.offset) + ) + self.__instruction_list.append(immutable_instruction) + + @property + def instructions(self) -> List[Instruction]: + return self.__instruction_list.copy() + + @property + def return_ip(self) -> InstructionPointer: + return self.__return_ip + + diff --git a/qctoolkit/pulses/repetition_pulse_template.py b/qctoolkit/pulses/repetition_pulse_template.py index 7560629fb..6f8b79898 100644 --- a/qctoolkit/pulses/repetition_pulse_template.py +++ b/qctoolkit/pulses/repetition_pulse_template.py @@ -7,7 +7,7 @@ from qctoolkit.pulses.pulse_template import PulseTemplate, MeasurementWindow from qctoolkit.pulses.sequencing import Sequencer -from qctoolkit.pulses.instructions import InstructionBlock +from qctoolkit.pulses.instructions import InstructionBlock, InstructionPointer from qctoolkit.pulses.parameters import ParameterDeclaration, Parameter from qctoolkit.pulses.conditions import Condition @@ -85,8 +85,11 @@ def build_sequence(self, if not repetition_count.is_integer(): raise ParameterNotIntegerException(self.__repetition_count.name, repetition_count) - for _ in range(0, int(repetition_count), 1): - sequencer.push(self.__body, parameters, conditions, instruction_block) + body_block = InstructionBlock() + body_block.return_ip = InstructionPointer(instruction_block, len(instruction_block)) + + instruction_block.add_instruction_repj(int(repetition_count), body_block) + sequencer.push(self.body, parameters, conditions, body_block) def requires_stop(self, parameters: Dict[str, Parameter], diff --git a/qctoolkit/pulses/sequencing.py b/qctoolkit/pulses/sequencing.py index eeb119537..e3ff82669 100644 --- a/qctoolkit/pulses/sequencing.py +++ b/qctoolkit/pulses/sequencing.py @@ -11,7 +11,7 @@ from typing import Tuple, Dict, Union, Optional import numbers -from qctoolkit.pulses.instructions import InstructionBlock, InstructionSequence +from qctoolkit.pulses.instructions import InstructionBlock, ImmutableInstructionBlock from qctoolkit.pulses.parameters import Parameter, ConstantParameter @@ -144,17 +144,17 @@ def push(self, self.__sequencing_stacks[target_block].append((sequencing_element, parameters, conditions)) - def build(self) -> InstructionSequence: + def build(self) -> InstructionBlock: """Start the translation process. Translate all elements currently on the translation stacks - into an InstructionSequence. + 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 main sequence. + it will append new instructions to the previously generated and returned blocks. Returns: - The instruction sequence resulting from the translation of the (remaining) + The instruction block (hierarchy) resulting from the translation of the (remaining) SequencingElements on the sequencing stacks. """ if not self.has_finished(): @@ -171,7 +171,7 @@ def build(self) -> InstructionSequence: element.build_sequence(self, parameters, conditions, target_block) else: break - return self.__main_block.compile_sequence() + return ImmutableInstructionBlock(self.__main_block, dict()) def has_finished(self) -> bool: """Check whether all translation stacks are empty. Indicates that the translation is diff --git a/tests/pulses/conditions_tests.py b/tests/pulses/conditions_tests.py index 68e02011f..9d4bb779a 100644 --- a/tests/pulses/conditions_tests.py +++ b/tests/pulses/conditions_tests.py @@ -4,7 +4,7 @@ from qctoolkit.pulses.instructions import InstructionPointer, Trigger, CJMPInstruction, GOTOInstruction from qctoolkit.pulses.conditions import HardwareCondition, SoftwareCondition, ConditionEvaluationException -from tests.pulses.sequencing_dummies import DummySequencingElement, DummySequencer, DummyInstructionBlock +from tests.pulses.sequencing_dummies import DummySequencingElement, DummySequencer, DummyInstructionBlock, DummyInstruction class HardwareConditionTest(unittest.TestCase): @@ -12,6 +12,7 @@ class HardwareConditionTest(unittest.TestCase): def test_build_sequence_loop(self) -> None: sequencer = DummySequencer() block = DummyInstructionBlock() + block.add_instruction(DummyInstruction()) delegator = DummySequencingElement() body = DummySequencingElement() @@ -23,8 +24,8 @@ def test_build_sequence_loop(self) -> None: self.assertEqual(1, len(block.embedded_blocks)) body_block = block.embedded_blocks[0] - self.assertEqual([CJMPInstruction(trigger, body_block, 0)], block.instructions, "The expected conditional jump was not generated by HardwareConditon.") - self.assertEqual(InstructionPointer(block, 0), body_block.return_ip, "The return address of the loop body block was set wrongly by HardwareCondition.") + 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()) @@ -44,7 +45,7 @@ def test_build_sequence_branch(self) -> None: if_block = block.embedded_blocks[0] else_block = block.embedded_blocks[1] - self.assertEqual([CJMPInstruction(trigger, if_block, 0), GOTOInstruction(else_block, 0)], block.instructions, "The expected jump instruction were not generated by HardwareConditon.") + 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") diff --git a/tests/pulses/instructions_tests.py b/tests/pulses/instructions_tests.py index 9a8df560f..ea7687b70 100644 --- a/tests/pulses/instructions_tests.py +++ b/tests/pulses/instructions_tests.py @@ -1,8 +1,10 @@ import unittest -from qctoolkit.pulses.instructions import InstructionBlockAlreadyFinalizedException,InstructionBlock, InstructionPointer,\ - InstructionBlockNotYetPlacedException, Trigger, CJMPInstruction, GOTOInstruction,EXECInstruction, STOPInstruction,\ - MissingReturnAddressException, InstructionSequence +from typing import Dict, Any, List + +from qctoolkit.pulses.instructions import InstructionBlock, InstructionPointer,\ + Trigger, CJMPInstruction, REPJInstruction, GOTOInstruction, EXECInstruction, STOPInstruction,\ + InstructionSequence, AbstractInstructionBlock, ImmutableInstructionBlock, Instruction from tests.pulses.sequencing_dummies import DummyWaveform, DummyInstructionBlock @@ -20,19 +22,17 @@ def test_initialization_main_block(self) -> None: ip = InstructionPointer(block, offset) self.assertIs(block, ip.block) self.assertEqual(offset, ip.offset) - self.assertEqual(offset, ip.get_absolute_address()) - + def test_initialization_relative_block(self) -> None: - block = InstructionBlock().create_embedded_block() + block = InstructionBlock() for offset in [0, 1, 924]: ip = InstructionPointer(block, offset) self.assertIs(block, ip.block) self.assertEqual(offset, ip.offset) - self.assertRaises(InstructionBlockNotYetPlacedException, ip.get_absolute_address) - + def test_equality(self) -> None: blocks = [InstructionBlock(), InstructionBlock()] - blocks.append(blocks[0].create_embedded_block()) + blocks.append(InstructionBlock()) ips = [] for block in blocks: for offset in [0, 1, 2352]: @@ -62,7 +62,7 @@ def test_initialization(self) -> None: block = InstructionBlock() trigger = Trigger() for offset in [0, 1, 23]: - instr = CJMPInstruction(trigger, block, offset) + instr = CJMPInstruction(trigger, InstructionPointer(block, offset)) self.assertEqual(trigger, instr.trigger) self.assertEqual(block, instr.target.block) self.assertEqual(offset, instr.target.offset) @@ -70,15 +70,15 @@ def test_initialization(self) -> None: def test_equality(self) -> None: blocks = [InstructionBlock(), InstructionBlock()] for offset in [0, 1, 23]: - instrA = CJMPInstruction(0, blocks[0], offset) - instrB = CJMPInstruction(0, blocks[0], offset) + 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, block, offset) + instruction = CJMPInstruction(trigger, InstructionPointer(block, offset)) self.assertEqual(instruction, instruction) for other in instrs: self.assertNotEqual(instruction, other) @@ -89,30 +89,70 @@ def test_equality(self) -> None: def test_str(self) -> None: block = DummyInstructionBlock() trigger = Trigger() - instr = CJMPInstruction(trigger, block, 3) + 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: + 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(block, offset) + 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(blocks[0], offset) - instrB = GOTOInstruction(blocks[0], offset) + 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(block, offset) + instruction = GOTOInstruction(InstructionPointer(block, offset)) self.assertEqual(instruction, instruction) for other in instrs: self.assertNotEqual(instruction, other) @@ -122,7 +162,7 @@ def test_equality(self) -> None: def test_str(self) -> None: block = DummyInstructionBlock() - instr = GOTOInstruction(block, 3) + instr = GOTOInstruction(InstructionPointer(block, 3)) self.assertEqual("goto to {}".format(str(InstructionPointer(block, 3))), str(instr)) @@ -168,46 +208,144 @@ def test_equality(self): 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]) + self.assertRaises(IndexError, block.__getitem__, 1) + self.assertEqual(STOPInstruction(), block[-1]) + self.assertRaises(IndexError, block.__getitem__, -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]) + self.assertRaises(IndexError, block.__getitem__, 1) + self.assertEqual(GOTOInstruction(InstructionPointer(parent_block, 84)), block[-1]) + self.assertRaises(IndexError, block.__getitem__, -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]) + self.assertRaises(IndexError, block.__getitem__, 2) + self.assertEqual(STOPInstruction(), block[-1]) + self.assertEqual(EXECInstruction(wf), block[-2]) + self.assertRaises(IndexError, block.__getitem__, -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]) + self.assertRaises(IndexError, block.__getitem__, 2) + self.assertEqual(GOTOInstruction(InstructionPointer(parent_block, 29)), block[-1]) + self.assertEqual(EXECInstruction(wf), block[-2]) + self.assertRaises(IndexError, block.__getitem__, -3) + + class InstructionBlockTest(unittest.TestCase): - def __verify_block(self, block: InstructionBlock, expected_instructions: InstructionSequence, expected_compiled_instructions: InstructionSequence) -> None: - self.assertEqual(len(expected_instructions), len(block)) + 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, block.compile_sequence()) + self.assertEqual(expected_compiled_instructions, [x for x in block]) + self.assertEqual(expected_return_ip, block.return_ip) - def test_empty_unreturning_main_block(self) -> None: + def test_empty_unreturning_block(self) -> None: block = InstructionBlock() - self.__verify_block(block, [], [STOPInstruction()]) - self.assertEqual(0, block.get_start_address()) - self.assertIsNone(block.return_ip) - self.__verify_block(block, [], [STOPInstruction()]) + self.__verify_block(block, [], [STOPInstruction()], None) - def test_empty_returning_main_block(self) -> None: - block = InstructionBlock() - self.assertEqual(0, block.get_start_address()) - self.assertIsNone(block.return_ip) - ip = InstructionPointer(InstructionBlock(), 7) - block.return_ip = ip # must have no effect - self.__verify_block(block, [], [STOPInstruction()]) - - def test_empty_relative_block(self) -> None: + def test_empty_returning_block(self) -> None: return_block = InstructionBlock() - block = InstructionBlock(return_block) - self.assertRaises(InstructionBlockNotYetPlacedException, block.get_start_address) - self.assertRaises(MissingReturnAddressException, block.compile_sequence) + block = InstructionBlock() ip = InstructionPointer(return_block, 7) block.return_ip = ip - self.__verify_block(block, [], [GOTOInstruction(ip.block, ip.offset)]) + self.__verify_block(block, [], [GOTOInstruction(ip)], ip) def test_create_embedded_block(self) -> None: parent_block = InstructionBlock() - block = parent_block.create_embedded_block() - self.assertRaises(InstructionBlockNotYetPlacedException, block.get_start_address) - self.assertRaises(MissingReturnAddressException, block.compile_sequence) - block.return_ip = InstructionPointer(parent_block, 0) - self.__verify_block(block, [], [GOTOInstruction(parent_block, 0)]) - self.__verify_block(parent_block, [], [STOPInstruction(), GOTOInstruction(parent_block, 0)]) - self.assertEqual(1, block.get_start_address()) + 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() @@ -223,39 +361,52 @@ def test_add_instruction_exec(self) -> None: expected_compiled_instructions = expected_instructions.copy() expected_compiled_instructions.append(STOPInstruction()) - self.__verify_block(block, expected_instructions, expected_compiled_instructions) + self.__verify_block(block, expected_instructions, expected_compiled_instructions, None) def test_add_instruction_goto(self) -> None: block = InstructionBlock() expected_instructions = [] - targets = [(InstructionBlock(), 0), (InstructionBlock(), 1), (InstructionBlock(), 50)] + 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(target[0], target[1]) + instruction = GOTOInstruction(InstructionPointer(target)) expected_instructions.append(instruction) - block.add_instruction_goto(target[0], target[1]) + 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) + self.__verify_block(block, expected_instructions, expected_compiled_instructions, None) def test_add_instruction_cjmp(self) -> None: block = InstructionBlock() expected_instructions = [] - expected_compiled_instructions = [] - targets = [(InstructionBlock(), 0), (InstructionBlock(), 1), (InstructionBlock(), 50)] + 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]], targets[i[0]], 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) + 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() @@ -264,12 +415,13 @@ def test_add_instruction_stop(self) -> None: block.add_instruction_stop() expected_compiled_instructions = expected_instructions.copy() expected_compiled_instructions.append(STOPInstruction()) - self.__verify_block(block, expected_instructions, expected_compiled_instructions) + 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 = [] @@ -278,18 +430,22 @@ def test_nested_block_construction(self) -> None: main_block.add_instruction_exec(waveforms[0]) expected_instructions[0].append(EXECInstruction(waveforms[0])) - block = main_block.create_embedded_block() + block = InstructionBlock() trigger = Trigger() + ip = InstructionPointer(block) main_block.add_instruction_cjmp(trigger, block) - expected_instructions[0].append(CJMPInstruction(trigger, block, 0)) + 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 = main_block.create_embedded_block() + block = InstructionBlock() trigger = Trigger() + ip = InstructionPointer(block) main_block.add_instruction_cjmp(trigger, block) - expected_instructions[0].append(CJMPInstruction(trigger, block, 0)) + 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]] @@ -301,10 +457,12 @@ def test_nested_block_construction(self) -> None: expected_instructions[i + 1].append(EXECInstruction(waveform)) block.add_instruction_exec(waveform) - block = blocks[0].create_embedded_block() + block = InstructionBlock() + ip = InstructionPointer(block) blocks[0].add_instruction_cjmp(trigger, block) - expected_instructions[1].append(CJMPInstruction(trigger, block, 0)) + 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]: @@ -317,32 +475,16 @@ def test_nested_block_construction(self) -> None: expected_compiled_instructions[0].append(STOPInstruction()) for i in [0, 1, 2]: - expected_compiled_instructions[i + 1].append(GOTOInstruction(blocks[i].return_ip.block, blocks[i].return_ip.offset)) + expected_compiled_instructions[i + 1].append(GOTOInstruction(blocks[i].return_ip)) positions = [0, None, None, None] positions[3] = len(expected_compiled_instructions[1]) - - expected_compiled_instructions[1].extend(expected_compiled_instructions[3]) - for i in [1, 2]: - positions[i] = len(expected_compiled_instructions[0]) - expected_compiled_instructions[0].extend(expected_compiled_instructions[i]) - - positions[3] += positions[1] - - self.__verify_block(blocks[2], expected_instructions[3], expected_compiled_instructions[3]) - self.__verify_block(blocks[1], expected_instructions[2], expected_compiled_instructions[2]) - self.__verify_block(blocks[0], expected_instructions[1], expected_compiled_instructions[1]) - self.__verify_block(main_block, expected_instructions[0], expected_compiled_instructions[0]) - - self.assertEqual(positions[3], blocks[2].get_start_address()) - self.assertEqual(positions[2], blocks[1].get_start_address()) - self.assertEqual(positions[1], blocks[0].get_start_address()) - self.assertEqual(positions[0], main_block.get_start_address()) - - for instruction in main_block.instructions: - if isinstance(instruction, GOTOInstruction) or isinstance(instruction, CJMPInstruction): - self.assertIsInstance(instruction.target.get_absolute_address(), int) - + + 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() @@ -350,16 +492,131 @@ def test_equality(self) -> None: self.assertNotEqual(block1, block2) self.assertNotEqual(hash(block1), hash(block2)) - def test_add_instruction_already_compiled(self) -> None: + +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() - wf1 = DummyWaveform() - wf2 = DummyWaveform() - block.add_instruction_exec(wf1) - block.compile_sequence() - block.add_instruction_exec(wf2) - sequence = block.compile_sequence() - expected_sequence = [EXECInstruction(wf1), EXECInstruction(wf2), STOPInstruction()] - self.assertEqual(expected_sequence, sequence) + 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() + ip = InstructionPointer(block) + main_block.add_instruction_cjmp(trigger, block) + block.return_ip = InstructionPointer(main_block, len(main_block)) + blocks.append(block) + + block = InstructionBlock() + trigger = Trigger() + ip = InstructionPointer(block) + 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() + ip = InstructionPointer(block) + 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): @@ -374,9 +631,6 @@ def test_str(self) -> None: CJMPInstruction(T,IB), GOTOInstruction(IB), EXECInstruction(W), - InstructionBlockAlreadyFinalizedException(), - InstructionBlockNotYetPlacedException(), - MissingReturnAddressException(), IB ] diff --git a/tests/pulses/plotting_tests.py b/tests/pulses/plotting_tests.py index 103d89f99..b0267e110 100644 --- a/tests/pulses/plotting_tests.py +++ b/tests/pulses/plotting_tests.py @@ -17,10 +17,10 @@ def test_render_unsupported_instructions(self) -> None: block.add_instruction(DummyInstruction()) plotter = Plotter() with self.assertRaises(NotImplementedError): - plotter.render(block.compile_sequence()) + plotter.render(block) def test_render_no_waveforms(self) -> None: - self.assertEqual(([], []), Plotter().render(InstructionBlock().compile_sequence())) + self.assertEqual(([], []), Plotter().render(InstructionBlock())) def test_render(self) -> None: wf1 = DummyWaveform(duration=19) @@ -31,7 +31,7 @@ def test_render(self) -> None: block.add_instruction_exec(wf2) plotter = Plotter(sample_rate=0.5) - times, voltages = plotter.render(block.compile_sequence()) + times, voltages = plotter.render(block) wf1_expected = [([0, 2, 4, 6, 8, 10, 12, 14, 16, 18], 0)] wf2_expected = [([20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40], 1)] diff --git a/tests/pulses/repetition_pulse_template_tests.py b/tests/pulses/repetition_pulse_template_tests.py index abb711764..e9ca798e4 100644 --- a/tests/pulses/repetition_pulse_template_tests.py +++ b/tests/pulses/repetition_pulse_template_tests.py @@ -2,6 +2,7 @@ from qctoolkit.pulses.repetition_pulse_template import RepetitionPulseTemplate,ParameterNotIntegerException from qctoolkit.pulses.parameters import ParameterDeclaration, ParameterNotProvidedException, ParameterValueIllegalException +from qctoolkit.pulses.instructions import REPJInstruction, InstructionPointer from tests.pulses.sequencing_dummies import DummyPulseTemplate, DummySequencer, DummyInstructionBlock, DummyParameter, DummyCondition from tests.serialization_dummies import DummySerializer @@ -86,15 +87,25 @@ def test_build_sequence_constant(self) -> None: parameters = {} conditions = dict(foo=DummyCondition(requires_stop=True)) t.build_sequence(self.sequencer, parameters, conditions, self.block) - self.assertEqual([(self.body, parameters, conditions), (self.body, parameters, conditions), (self.body, parameters, conditions)], - self.sequencer.sequencing_stacks[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)], 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=3) conditions = dict(foo=DummyCondition(requires_stop=True)) self.template.build_sequence(self.sequencer, parameters, conditions, self.block) - self.assertEqual([(self.body, parameters, conditions), (self.body, parameters, conditions), (self.body, parameters, conditions)], - self.sequencer.sequencing_stacks[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)], + self.sequencer.sequencing_stacks[body_block]) + self.assertEqual([REPJInstruction(parameters['foo'], InstructionPointer(body_block, 0))], self.block.instructions) + def test_build_sequence_declaration_exceeds_bounds(self) -> None: parameters = dict(foo=9) diff --git a/tests/pulses/sequencing_dummies.py b/tests/pulses/sequencing_dummies.py index 781ff8ac4..ca1b86b69 100644 --- a/tests/pulses/sequencing_dummies.py +++ b/tests/pulses/sequencing_dummies.py @@ -5,7 +5,7 @@ """LOCAL IMPORTS""" from qctoolkit.serialization import Serializer -from qctoolkit.pulses.instructions import Waveform, Instruction +from qctoolkit.pulses.instructions import Waveform, Instruction, CJMPInstruction, GOTOInstruction, REPJInstruction from qctoolkit.pulses.sequencing import Sequencer, InstructionBlock, SequencingElement from qctoolkit.pulses.parameters import Parameter, ParameterDeclaration from qctoolkit.pulses.pulse_template import PulseTemplate, MeasurementWindow @@ -101,14 +101,14 @@ def compare_key(self) -> Any: class DummyInstructionBlock(InstructionBlock): - def __init__(self, outer_block: InstructionBlock = None) -> None: - super().__init__(outer_block) + def __init__(self) -> None: + super().__init__() self.embedded_blocks = [] # type: Collection[InstructionBlock] - def create_embedded_block(self) -> InstructionBlock: - block = InstructionBlock(self) - self.embedded_blocks.append(block) - return block + 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): diff --git a/tests/qcmatlab/pulse_control_tests.py b/tests/qcmatlab/pulse_control_tests.py index 7bfb7e08c..8268a7417 100644 --- a/tests/qcmatlab/pulse_control_tests.py +++ b/tests/qcmatlab/pulse_control_tests.py @@ -32,7 +32,7 @@ def test_create_pulse_group_empty(self) -> None: block = DummyInstructionBlock() pci = PulseControlInterface(sample_rate) - (result, _) = pci.create_pulse_group(block.compile_sequence(), name=name) + (result, _) = pci.create_pulse_group(block, name=name) expected_result = dict( name=name, nrep=[], @@ -58,7 +58,7 @@ def test_create_pulse_group(self) -> None: registering_function = lambda x: x['data'] pci = PulseControlInterface(sample_rate, time_scaling=1) - (result, _) = pci.create_pulse_group(block.compile_sequence(), name=name) + (result, _) = pci.create_pulse_group(block, name=name) expected_result = dict( name=name, nrep=[2, 1, 1], @@ -75,7 +75,7 @@ def test_create_pulse_group_invalid_instruction(self) -> None: name = 'foo_group' sample_rate = 10 block = DummyInstructionBlock() - block.add_instruction_goto(block.create_embedded_block()) + block.add_instruction_goto(DummyInstructionBlock()) pci = PulseControlInterface(sample_rate) with self.assertRaises(Exception):