Source code for c3.generator.generator

"""
Signal generation stack.

Contrary to most quanutm simulators, C^3 includes a detailed simulation of the control
stack. Each component in the stack and its functions are simulated individually and
combined here.

Example: A local oscillator and arbitrary waveform generator signal
are put through via a mixer device to produce an effective modulated signal.
"""

from typing import List, Callable, Dict
import hjson
import tensorflow as tf
from c3.c3objs import hjson_decode, hjson_encode
from c3.signal.gates import Instruction
from c3.generator.devices import devices as dev_lib
from c3.generator.devices import Device


[docs]class Generator: """ Generator, creates signal from digital to what arrives to the chip. Parameters ---------- devices : list Physical or abstract devices in the signal processing chain. resolution : np.float64 Resolution at which continuous functions are sampled. callback : Callable Function that is called after each device in the signal line. """ def __init__( self, devices: dict = None, chains: dict = None, resolution: float = 0.0, callback: Callable = None, ): self.devices: Dict[str, Device] = {} if devices: self.devices = devices self.chains: Dict[str, Dict[str, List[str]]] = {} self.sorted_chains: Dict[str, List[str]] = {} self.set_chains(chains) self.resolution = resolution self.callback = callback
[docs] def set_chains(self, chains: Dict[str, Dict[str, List[str]]]): if chains: self.chains = chains self.__check_signal_chains()
def __check_signal_chains(self) -> None: for channel, chain in self.chains.items(): signals = 0 for device_id, sources in chain.items(): # all source devices need to exist and have the same resolution if sources: res = self.devices[sources[0]].resolution for dev in sources: if dev not in self.devices: raise Exception(f"C3:Error: device {dev} not found.") if res != self.devices[dev].resolution: raise Exception( f"C3:Error: Different resolution of inputs in {channel} {device_id}:{sources}." ) # the expected number of inputs must match the connected devices if self.devices[device_id].inputs != len(sources): raise Exception( f"C3:Error: device {device_id} expects {self.devices[device_id].inputs} inputs, but {len(sources)} found." ) # overall the chain should have exactly 1 output signal signals -= self.devices[device_id].inputs signals += self.devices[device_id].outputs if signals != 1: raise Exception( "C3:ERROR: Signal chain for channel '" + channel + "' contains unmatched number of inputs and outputs." ) # bring chain in topological order self.sorted_chains[channel] = self.__topological_ordering( self.chains[channel] ) def __topological_ordering(self, predecessors: Dict[str, List[str]]) -> List[str]: """ Computes the topological ordering of a directed acyclic graph. Parameters ---------- predecessors : dict list of preceding nodes for each node Returns ------- a list of all nodes in topological ordering Raises ------ ValueError if the graph contains a cycle """ stack = [x for x in predecessors if len(predecessors[x]) == 0] num_sources = {node: len(predecessors[node]) for node in predecessors} successors = {} for node in predecessors: successors[node] = [x for x in predecessors if node in predecessors[x]] ordered = [] while stack: src = stack.pop() for node in successors[src]: num_sources[node] -= 1 if num_sources[node] == 0: stack.append(node) ordered.append(src) if len(ordered) != len(successors): raise Exception("C3:ERROR: Device chain contains a cycle") return ordered
[docs] def read_config(self, filepath: str) -> None: """ Load a file and parse it to create a Generator object. Parameters ---------- filepath : str Location of the configuration file """ with open(filepath, "r") as cfg_file: cfg = hjson.loads(cfg_file.read(), object_pairs_hook=hjson_decode) self.fromdict(cfg)
[docs] def fromdict(self, cfg: dict) -> None: for name, props in cfg["Devices"].items(): dev_type = props.pop("c3type") if "name" not in props: props["name"] = name self.devices[name] = dev_lib[dev_type](**props) self.chains = cfg["Chains"] self.__check_signal_chains()
[docs] def write_config(self, filepath: str) -> None: """ Write dictionary to a HJSON file. """ with open(filepath, "w") as cfg_file: hjson.dump(self.asdict(), cfg_file, default=hjson_encode)
[docs] def asdict(self) -> dict: """ Return a dictionary compatible with config files. """ devices = {} for name, dev in self.devices.items(): devices[name] = dev.asdict() return {"Devices": devices, "Chains": self.chains}
[docs] def __str__(self) -> str: return hjson.dumps(self.asdict(), default=hjson_encode)
[docs] def generate_signals(self, instr: Instruction) -> dict: """ Perform the signal chain for a specified instruction, including local oscillator, AWG generation and IQ mixing. Parameters ---------- instr : Instruction Operation to be performed, e.g. logical gate. Returns ------- dict Signal to be applied to the physical device. """ gen_signal: Dict[str, Dict[str, tf.constant]] = {} signal_stack: Dict[str, Dict[str, tf.constant]] = {} for chan in instr.comps: chain = self.chains[chan] signal_stack[chan] = {} # create list of succeeding devices successors = {} for dev_id in chain: successors[dev_id] = [x for x in chain if dev_id in chain[x]] for dev_id in self.sorted_chains[chan]: # collect inputs sources = self.chains[chan][dev_id] inputs = [signal_stack[chan][x] for x in sources] # calculate the output and store it in the stack dev = self.devices[dev_id] output = dev.process(instr, chan, inputs) signal_stack[chan][dev_id] = output # remove inputs if they are not needed anymore # for source in sources: # successors[source].remove(dev_id) # if len(successors[source]) < 1: # del signal_stack[chan][source] # call the callback with the current signal if self.callback: self.callback(chan, dev_id, output) gen_signal[chan] = {} for key in output.keys(): gen_signal[chan][key] = tf.identity(output[key]) # Hack to use crosstalk. Will be generalized to a post-processing module. # TODO: Rework of the signal generation for larger chips, similar to qiskit if "crosstalk" in self.devices: gen_signal = self.devices["crosstalk"].process( None, None, signals=[gen_signal] ) return gen_signal