<commit_before>'''
functions for compiling lists of pulses/pulseBlocks down to the hardware level.

Copyright 2013 <NAME>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''
import logging
import numpy as np
import os
import operator
from warnings import warn
from copy import copy
from functools import reduce
from importlib import import_module
import json

from . import config
from . import PatternUtils
from .PatternUtils import flatten
from . import Channels
from . import ChannelLibrary
from . import PulseShapes
from .PulsePrimitives import Id
from .PulseSequencer import Pulse, PulseBlock, CompositePulse
from . import ControlFlow
from . import BlockLabel

logger = logging.getLogger(__name__)

def map_logical_to_physical(wires):
    # construct a mapping of physical channels to lists of logical channels
    # (there will be more than one logical channel if multiple logical
    # channels share a physical channel)
    physicalChannels = {}
    for logicalChan in wires.keys():
        physChan = logicalChan.physChan
        if physChan not in physicalChannels:
            physicalChannels[physChan] = [logicalChan]
        else:
            physicalChannels[physChan].append(logicalChan)

    # loop through the physical channels
    physicalWires = {}
    for physChan, logicalChan in physicalChannels.items():
        if len(logicalChan) > 1:
            physicalWires[physChan] = merge_channels(wires, logicalChan)
        else:
            physicalWires[physChan] = wires[logicalChan[0]]

    return physicalWires


def merge_channels(wires, channels):
    chan = channels[0]
    mergedWire = [[] for _ in range(len(wires[chan]))]
    shapeFunLib = {}
    for ct, segment in enumerate(mergedWire):
        entryIterators = [iter(wires[ch][ct]) for ch in channels]
        while True:
            try:
                entries = [next(e) for e in entryIterators]
            except StopIteration:
                break
            # control flow on any channel should pass thru
            if any(isinstance(e, (ControlFlow.ControlInstruction,
                                  BlockLabel.BlockLabel)) for e in entries):
                # for the moment require uniform control flow so that we
                # can pull from the first channel
                assert all(e == entries[0]
                           for e in entries), "Non-uniform control flow"
                segment.append(entries[0])
                continue
            # at this point we have at least one waveform instruction
            blocklength = pull_uniform_entries(entries, entryIterators)

            # look for the simplest case of at most one non-identity
            nonZeroEntries = [e for e in entries if not e.isZero]
            if len(nonZeroEntries) == 0:
                segment.append(entries[0])
                continue
            elif len(nonZeroEntries) == 1:
                segment.append(nonZeroEntries[0])
                continue

            # create a new Pulse object for the merged pulse

            # If there is a non-zero SSB frequency copy it to the new entry
            nonZeroSSBChan = np.nonzero(
                [e.amp * e.frequency for e in entries])[0]
            assert len(nonZeroSSBChan) <= 1, \
                "Unable to handle merging more than one non-zero entry with non-zero frequency."
            if nonZeroSSBChan:
                frequency = entries[nonZeroSSBChan[0]].frequency
            else:
                frequency = 0.0

            if all([e.shapeParams['shapeFun'] == PulseShapes.constant for e in entries]):
                phasor = np.sum([e.amp * np.exp(1j * e.phase) for e in entries])
                amp = np.abs(phasor)
                phase = np.angle(phasor)
                shapeFun = PulseShapes.constant
            else:
                amp = 1.0
                phase = 0.0
                pulsesHash = tuple([(e.hashshape(), e.amp, e.phase) for e in entries])
                if pulsesHash not in shapeFunLib:
                    # create closure to sum waveforms
                    def sum_shapes(entries=entries, **kwargs):
                        return reduce(operator.add,
                            [e.amp * np.exp(1j * e.phase) * e.shape for e in entries])

                    shapeFunLib[pulsesHash] = sum_shapes
                shapeFun = shapeFunLib[pulsesHash]

            shapeParams = {"shapeFun": shapeFun, "length": blocklength}

            label = "*".join([e.label for e in entries])
            segment.append(Pulse(label, entries[0].channel, shapeParams, amp, phase))

    return mergedWire


def pull_uniform_entries(entries, entryIterators):
    '''
    Given entries from a set of logical channels (that share a physical
    channel), pull enough entries from each channel so that the total pulse
    length matches. e.g. if your entry data was like this:
        ch1 : | A1 | B1 | C1 |      D1     | ...
        ch2 : |      A2      |      B2     | ...

    and initially entries = [A1, A2], we would construct
        A1* = A1 + B1 + C1
    and update entries such that entries = [A1*, A2].
    The function returns the resulting block length.
    '''
    numChan = len(entries)
    #keep track of how many entry iterators are used up
    iterDone = [ False ] * numChan
    ct = 0
    while True:
        #If we've used up all the entries on all the channels we're done
        if all(iterDone):
            raise StopIteration("Unable to find a uniform set of entries")

        #If all the entry lengths are the same we are finished
        entryLengths = [e.length for e in entries]
        if all(x == entryLengths[0] for x in entryLengths):
            break

        #Otherwise try to concatenate on entries to match lengths
        while entries[ct].length < max(e.length for e in entries):
            # concatenate with following entry to make up the length difference
            try:
                nextentry = next(entryIterators[ct])
            except StopIteration:
                iterDone[ct] = True

            entries[ct] = concatenate_entries(entries[ct], nextentry)

        ct = (ct + 1) % numChan

    return max(e.length for e in entries)


def concatenate_entries(entry1, entry2):
    # TA waveforms with the same amplitude can be merged with a just length update
    # otherwise, need to concatenate the pulse shapes
    shapeParams = copy(entry1.shapeParams)
    shapeParams["length"] = entry1.length + entry2.length
    label = entry1.label
    amp = entry1.amp
    phase = entry1.phase<commit_msg>Check no. of non-0 entries in pull_uniform_entries<commit_after>'''
functions for compiling lists of pulses/pulseBlocks down to the hardware level.

Copyright 2013 <NAME>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''
import logging
import numpy as np
import os
import operator
from warnings import warn
from copy import copy
from functools import reduce
from importlib import import_module
import json

from . import config
from . import PatternUtils
from .PatternUtils import flatten
from . import Channels
from . import ChannelLibrary
from . import PulseShapes
from .PulsePrimitives import Id
from .PulseSequencer import Pulse, PulseBlock, CompositePulse
from . import ControlFlow
from . import BlockLabel

logger = logging.getLogger(__name__)

def map_logical_to_physical(wires):
    # construct a mapping of physical channels to lists of logical channels
    # (there will be more than one logical channel if multiple logical
    # channels share a physical channel)
    physicalChannels = {}
    for logicalChan in wires.keys():
        physChan = logicalChan.physChan
        if physChan not in physicalChannels:
            physicalChannels[physChan] = [logicalChan]
        else:
            physicalChannels[physChan].append(logicalChan)

    # loop through the physical channels
    physicalWires = {}
    for physChan, logicalChan in physicalChannels.items():
        if len(logicalChan) > 1:
            physicalWires[physChan] = merge_channels(wires, logicalChan)
        else:
            physicalWires[physChan] = wires[logicalChan[0]]

    return physicalWires


def merge_channels(wires, channels):
    chan = channels[0]
    mergedWire = [[] for _ in range(len(wires[chan]))]
    shapeFunLib = {}
    for ct, segment in enumerate(mergedWire):
        entry_iterators = [iter(wires[ch][ct]) for ch in channels]
        while True:
            try:
                entries = [next(e) for e in entry_iterators]
            except StopIteration:
                break
            # control flow on any channel should pass thru
            if any(isinstance(e, (ControlFlow.ControlInstruction,
                                  BlockLabel.BlockLabel)) for e in entries):
                # for the moment require uniform control flow so that we
                # can pull from the first channel
                assert all(e == entries[0]
                           for e in entries), "Non-uniform control flow"
                segment.append(entries[0])
                continue
            # at this point we have at least one waveform instruction
            entries, block_length = pull_uniform_entries(entries, entry_iterators)

            # look for the simplest case of at most one non-identity
            if len(entries) == 1:
                segment.extend(entries[0])
                continue

            # create a new Pulse object for the merged pulse

            # If there is a non-zero SSB frequency copy it to the new entry
            nonZeroSSBChan = np.nonzero(
                [e.amp * e.frequency for e in entries])[0]
            assert len(nonZeroSSBChan) <= 1, \
                "Unable to handle merging more than one non-zero entry with non-zero frequency."
            if nonZeroSSBChan:
                frequency = entries[nonZeroSSBChan[0]].frequency
            else:
                frequency = 0.0  

            if all([e.shapeParams['shapeFun'] == PulseShapes.constant for e in entries]):
                phasor = np.sum([e.amp * np.exp(1j * e.phase) for e in entries])
                amp = np.abs(phasor)
                phase = np.angle(phasor)
                shapeFun = PulseShapes.constant
            else:
                amp = 1.0
                phase = 0.0
                pulsesHash = tuple([(e.hashshape(), e.amp, e.phase) for e in entries])
                if pulsesHash not in shapeFunLib:
                    # create closure to sum waveforms
                    def sum_shapes(entries=entries, **kwargs):
                        return reduce(operator.add,
                            [e.amp * np.exp(1j * e.phase) * e.shape for e in entries])

                    shapeFunLib[pulsesHash] = sum_shapes
                shapeFun = shapeFunLib[pulsesHash]

            shapeParams = {"shapeFun": shapeFun, "length": block_length}

            label = "*".join([e.label for e in entries])
            segment.append(Pulse(label, entries[0].channel, shapeParams, amp, phase))

    return mergedWire


def pull_uniform_entries(entries, entry_iterators):
    '''
    Given entries from a set of logical channels (that share a physical
    channel), pull enough entries from each channel so that the total pulse
    length matches. e.g. if your entry data was like this:
        ch1 : | A1 | B1 | C1 |      D1     | ...
        ch2 : |      A2      |      B2     | ...

    and initially entries = [A1, A2], we would construct
        A1* = A1 + B1 + C1
    and update entries such that entries = [A1*, A2].
    The function returns the resulting block length.
    '''
    numChan = len(entries)
    #keep track of how many entry iterators are used up
    iterDone = [ False ] * numChan
    ct = 0
    lengths = [e.length for e in entries]
    entries_stack = [[e] for e in entries]

    while True:
        #If we've used up all the entries on all the channels we're done
        if all(iterDone):
            raise StopIteration("Unable to find a uniform set of entries")

        if all(x == lengths[0] for x in lengths):
            break

        #Otherwise try to concatenate on entries to match lengths
        while lengths[ct] < max(lengths):
            # concatenate with following entry to make up the length difference
            try:
                next_entry = next(entry_iterators[ct])
            except StopIteration:
                iterDone[ct] = True

            entries_stack[ct].append(next_entry)
            lengths[ct] += next_entry.length

        ct = (ct + 1) % numChan


    #if we have all zeros or a single non zero we return that as a list of entries
    allzero_chan = [all([e.isZero for e in stack]) for stack in entries_stack ]
    if all(allzero_chan):
        entries = [ entries_stack[0] ]
    elif np.sum(allzero_chan) == len(entries) - 1:
        entries = [ entries_stack[allzero_chan.index(False)] ]
    else:
        entries = []
        for stack in entries_stack:
            entries.append( reduce(concatenate_entries, stack) )

    return entries, max(lengths)


def concatenate_entries(entry1, entry2):
    # TA waveforms with the same amplitude can be merged with a just length update
    # otherwise, need to concatenate the pulse shapes
    shapeParams = copy(entry1.shapeParams)
    shapeParams["length"] = entry1.length + entry2.length
    label = entry1.label
    amp = entry1.amp
    phase = entry1.phase
{'fraction_non_alphanumeric': 0.07322014951442744, 'fraction_numerical': 0.00789492070146021, 'mean_word_length': 2.207978484984312, 'pattern_counts': {'":': 4, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3166407', 'n_tokens_mistral': 3943, 'n_tokens_neox': 3695, 'n_words': 1419}