Raw File
sched.pl
#!/usr/bin/perl

###################################
#   Perl scheduler for ScaffCC
###################################
#   By: Jeff Heckey
#   Copyright: 2014 UCSB
#   NOT INTENDED FOR PUBLIC DISTRIBUTION
###################################
#
# Status (2/Jul/14):
#   Added width to RCP
# Status (3/Jun/14):
#   Added move counts to metrics output
# Status (26/May/14):
#   RCP optimizations round 1
# Status (22/May/14):
#   Added "true" printing, which prints the op schedule of each simd region at a given timestep
# Status (13/May/14):
#   Added tgate counts, modified output metrics to include more raw data
#   Added flags for pretty print
#   Minor optimizations (variable localization)
# Status (10/May/14):
#   update_moves optimized
#   other misc optimizations
# Status (7/May/14):
#   find_lp now uses $obj->{ops} to iterate instead of ACPA schedule. ~33% system time speedup
# Status (5/May/14):
#   Modified lpfs update ready to use hash instead of arrays (>10x speed up!)
# Status (2/May/14):
#   Got rid of moves from stalled SIMD regions
#   LPFS will SIMD sched opp regions
#   Tested SIMD L=1, K=2; L=1, K=4
#   Update 2: regression proofing for reschedule
# Status (1/May/14):
#   Improved find_lp algorithm, now much faster
# Status (29/Apr/14):
#   Enhancements to work with BWT (div/0 errors)
#   Profiled code to speed it up (find_LP() is longest runtime)
# Status (26/Apr/14):
#   Added metrics
# Status (25/Apr/14):
#   RCP now using update_moves and revised update RCPQ and general clean up
#   Update 2: Find next LP for completed SIMD region
#   Update 3: Opportunistic SIMD scheduling added
# Status (24/Apr/14):
#   Some partial fixes to schedule object for repeated passes.
# Status (23/Apr/14):
#   Finished LPFS with moves and cleaned up
# Status (8/Apr/14):
#   Finished a first pass of the LPFS scheduling
#   Non-optimal - could recalculate longest paths from present point if a path is completed.
# Status (18/Mar/14):
#   Contains CPR code for evaluating potential conflicts in SIMD regions
#   Partially completed RCP code, in more structured form
#

#use 5.012;

package Qubit;

sub new {
    my $class = shift;
    my $qubit = { 'name'      => "",
                  'index'     => 0,
                  'size'      => 1,
                  'loc'       => 0,  # RCP [-1 = unalloc, 0 = mem, 1-n = simd-n]
                  'last_op'   => -1,
                  'ops'       => [] };
    $qubit->{name} = shift;
    if ( $qubit->{name} =~ /(.*?)(\d+)/ ) {
        $qubit->{index} = $2;
    } else {
        $qubit->{index} = -1;
    }
    bless $qubit, $class;
    return $qubit;
}

sub add_dep {
    my $qubit = shift;
    push ( @{ $qubit->{ops} }, shift );
}

sub get_dep {
    my $qubit = shift;
    my $idx = shift || -1;  # return last element by default
    return $qubit->{ops}->[$idx];
}

# Fetches qubit at latest timestep
# Return: 1 if qubit needs to be moved, 0 otherwise
sub rcp_fetch_qubit {
    my $qubit = shift;
    my $ts = shift;
    my $simd = shift;
    my $op = shift;
    $qubit->{last_op}++;
    if ( !defined $qubit->{ops}->[ $qubit->{last_op} ] ) {
        printf "E: looking at $qubit->{name} out of range\n";
    } elsif ( ::refaddr( $op ) != ::refaddr( $qubit->{ops}->[$qubit->{last_op}] ) ) {
        printf "E: Out of order scheduling of qubit %s ops '%s' and '%s'\n",
                $qubit->{name}, $op->{text}, $qubit->{ops}->[$qubit->{last_op}]->{text};
    }
    $qubit->{last_ts} = $ts;
    if ( $qubit->{loc} != -1 and $qubit->{loc} != $simd ) {
        $qubit->{loc} = $simd;
        return 1;
    }
    $qubit->{loc} = $simd;
    return 0;
}

# Moves qubit back to memory if it is not used in this timestep
# Return: 1 if qubit returns to memory
sub rcp_store_qubit {
    my $qubit = shift;
    my $curr_ts = shift;
    if ( $qubit->{last_ts} != $curr_ts ) {
        $qubit->{loc} = 0;  # move to memory
        return 1;
    }
    return 0;   # Used in current timestep, so do not move to memory
}


package Op;

my $id = 1;

sub new {
    my $class = shift;
    my $op = {  'op'        => '',
                'rank'      => 0,
                'length'    => 1,
                'dist'      => -1,
                'top'       => 1,
                'bottom'    => 1,
                'args'      => [],
                'text'      => '',
                'id'        => 0,
                'slack'     => 0,
                'asap'      => {'ts'    => -1},
                'alap'      => {
                                'ts'    => -1,
                                'sched' => 0
                            },
                'acap'      => {
                                'ts'    => -1,
                                'sched' => 0
                            },
                'lpfs'      => {
                                'tag'       => 0,
                                'path'      => 0,
                                'followed'  => 0,
                                'simd'      => -1,
                                'ts'        => -1
                            },
                'rcp'       => {
                                'simd'      => -1,
                                'ts'        => -1
                            },
                'p_chaco'   => -1,
                'in_edges'  => [],
                'out_edges' => [] };
    $op->{op} = shift;
    $op->{rank} = shift;
    $op->{length} = shift || 1;
    $op->{id} = $id++   if ( $op->{op} ne "MOV" );
    $op->{text} = "$op->{id}: $op->{op}";
    bless $op, $class;
    return $op;
}

# Add new args and updated dependencies as needed
sub add_qubit {
    my $op = shift;
    my $qubit = shift;
    push @{ $op->{args} }, $qubit;
    if ( my $dep = $qubit->get_dep(-2) ) {
        push ( @{ $op->{'in_edges'} }, $dep );
        push ( @{ $dep->{'out_edges'} }, $op );
        $op->{top} = 0;
        $dep->{bottom} = 0;
    }
    $op->{'text'} .= " $qubit->{name}";
}

sub print_fields {
    my $op = shift;
    foreach my $field ( @_ ) {
        print  "$op->{$field} ";
    }
    print  "\n";
}

sub op_ready {
    my $op = shift;
    my $sched = shift;
    my $ready = 1;
    foreach my $parent ( @{ $op->{in_edges} } ) {
        $ready = 0  if ( ! defined $parent->{$sched}->{ts} or $parent->{$sched}->{ts} == -1 );
    }
    return $ready;
}

sub get_ready_children {
    my $op = shift;
    my $sched = shift;
    my @ready = ();
    foreach my $child ( @{ $op->{out_edges} } ) {
        push @ready, $child     if ( $child->op_ready($sched) );
    }
    return @ready;
}

sub take_path {
    my $op = shift;
    my $path = shift;
    $op->{lpfs}->{dist} = 0;   # Clear distance for next path finding
    $op->{lpfs}->{path} = $path;
    $op->{lpfs}->{simd} = $path;
    $op->{lpfs}->{followed} = 1;
}

# Fetch op's qubits if needed
# Return: list of qubits that need to move
sub rcp_fetch_op {
    my $op = shift;
    my $ts = shift;
    my $simd = shift;
    my @moves = ();
    $op->{rcp}->{ts} = $ts;
    $op->{rcp}->{simd} = $simd;
    foreach my $qubit ( @{ $op->{args} } ) {
        push @moves, $qubit     if ( $qubit->rcp_fetch_qubit($ts, $simd, $op) );
    }
    return @moves;
}

# Store op's qubits if needed
# Return: list of qubits that need to move
sub rcp_store_op {
    my $op = shift;
    my $ts = shift;
    my @moves = ();
    foreach my $qubit ( @{ $op->{args} } ) {
        push @moves, $qubit     if ( $qubit->rcp_store_qubit($ts) );
    }
    return @moves;
}


package Schedule;

sub new {
    my $class = shift;
    my $obj = ();
    $obj->{function} = shift;
    bless $obj, $class;
    $obj->{twidth} = 0; # cell width for printing
    $obj->{threads} = 0; # max number of parallel ops
    $obj->{length} = 0;
    $obj->{top} = [];
    $obj->{bottom} = {};
    $obj->{first_op} = 0;
    $obj->{last_op} = 0;
    $obj->{op_cnt} = 0;
    $obj->{qubits} = ();
    $obj->{active_qubits} = {};
    $obj->{ops} = [];
    $obj->{asap} = {threads => 0};
    $obj->{alap} = {threads => 0};
    $obj->{ss} = $obj->new_ss();
    $obj->{lpfs} = $obj->new_lpfs();
    $obj->{rcp} = $obj->new_rcp();
    return $obj;
}

sub new_ss {
    my $obj = shift;
    my $k = shift || $::SIMD_K;
    my $d = shift || $::SIMD_D;
    return {
                    op_cnt      => 0,   # counts gates and moves
                    moves       => 0,   # counts each move op
                    mts         => 0,   # counts only timesteps with moves
                    len         => 0,   # total time
                    tgates      => 0,   # timesteps that have a T gate
                    width       => 0,   # max SIMD regions
                    simd_k      => $k,
                    simd_d      => $d
                  };
}

sub new_lpfs {
    my $obj = shift;
    my $k = shift || $::SIMD_K;
    my $d = shift || $::SIMD_D;
    my $l = shift || $::SIMD_L;
    my $refill = shift || $::refill;
    my $opp = shift || $::opp;
    foreach my $op ( @{ $obj->{ops} } ) { $op->{lpfs}->{followed} = 0; }
    return {
                    op_cnt      => 0,   # counts gates and moves
                    moves       => 0,   # counts each move op
                    mts         => 0,   # counts only timesteps with moves
                    len         => 0,   # total time
                    tgates      => 0,   # timesteps that have a T gate
                    width       => 0,   # max SIMD regions
                    simd_k      => $k,
                    simd_d      => $d,
                    simd_l      => $l,  # simd regions to allocate only to paths
                    refill_simd => $refill,
                    opp_simd    => $opp,
                    path        => 1    # starting path id
                  };
}

sub new_rcp {
    my $obj = shift;
    my $k = shift || $::SIMD_K;
    my $d = shift || $::SIMD_D;
    my $w_op = shift || $::w_op;
    my $w_dist = shift || $::w_dist;
    my $w_slack = shift || $::w_slack;
    return {
                    op_cnt  => 0,   # counts gates and moves
                    moves   => 0,   # counts each move op
                    mts     => 0,   # counts only timesteps with moves
                    len     => 0,   # last timestep
                    tgates  => 0,   # timesteps that have a T gate
                    width   => 0,   # max SIMD regions
                    simd_k  => $k,
                    simd_d  => $d,
                    w_op    => $w_op,
                    w_dist  => $w_dist,
                    w_slack => $w_slack
                  };
}

# Add an operation to the function
# Operation will be ASAP scheduled
sub add_op {
    my $obj = shift;
    my $op = shift;
    my $time = 0;
    my $dist = 1;
    push @{ $obj->{ops} }, $op;
    if ( $op->{top} ) {
        push @{ $obj->{top} }, $op;
        $op->{dist} = $dist;
    } else {
        foreach my $pre ( @{$op->{in_edges}} ) {
            print  "W: '$pre->{text}'\@$pre->{asap}->{ts} not assigned\n"  if ($pre->{asap}->{ts} == -1);
            $time = ($time, $pre->{asap}->{ts} + $pre->{length})[$time < $pre->{asap}->{ts} + $pre->{length}];
            $dist = ($dist, $pre->{dist} + 1)[$dist < $pre->{dist} + 1];
            $pre->{bottom} = 0  if ( $pre->{bottom} );
            delete $obj->{bottom}->{$pre};
        }
        $op->{dist} = $dist;
    }
    $obj->{bottom}->{$op} = $op   if ( $op->{bottom} );
    $op->{asap}->{ts} = $time;
    $op->{lpfs}->{dist} = $dist;
    push @{ $obj->{asap}->{$time} }, $op;
    #$obj->{asap}->{threads} = ::max ($obj->{asap}->{threads}, scalar @{ $obj->{asap}->{$time} });
    $obj->{first_op} = $op      if ( $obj->{asap}->{first_op} == 0 );
    $obj->{last_op} = $op;
    $obj->{twidth} = ::max ($obj->{twidth}, length $op->{text});
    $obj->{length} = ($obj->{length}, $time+$op->{length})[$obj->{length} < $time+$op->{length}];
    $obj->{asap}->{op_cnt}++;
    $obj->{op_cnt}++;
}

sub draw_graph {
    my $obj = shift;
    my $file = shift;
    my @colors = qw(white red orange yellow green blue indigo violet pink 
                    darkorange gold limegreen cyan navy magenta coral 
                    orangered darkgoldenrod chartreuse skyblue turquoise 
                    plum);
    open DOT, ">$file" or die "Unable to open Dot file $file\n";
    # Print some header stuff
    # Make the function name the root node and an "exit" node
    print DOT <<HEADING;
digraph $obj->{function} {
    graph [ rankdir=LR ];

    Top \t[ label=\"$obj->{function}\", rank=0 ];
    Bottom\t[ label=\"Return\", rank=$obj->{length} ];
HEADING
    # Loop over {ops} for all intermediate nodes
    foreach my $op ( @{ $obj->{ops} } ) {
        #print DOT "    $op->{id}\t[ label=\"$op->{op}\" ];\n";
        print DOT "    $op->{id}\t[ label=\"$op->{op}$op->{id}\"";
        print DOT ", style=filled, fillcolor=$colors[$op->{lpfs}->{path}]";
        print DOT ", rank=$op->{dist}";
        print DOT " ];\n";
    }
    print DOT "\n";
    # Loop over all qubits for each edge (safer than trying to infer/rebuild from op perspective)
    while ( my ($name, $qubit) = each %{ $obj->{qubits} } ) {
        my $last = {'id' => 'Top'};
        foreach my $op ( @{ $qubit->{ops} } ) {
            print DOT "    $last->{id} -> $op->{id}\t[ label=\"$name\" ];\n";
            $last = $op;
        }
        print DOT "    $last->{id} -> Bottom\t[ label=\"$name\" ];\n";
    }
    print DOT "}";
    close DOT;
}

# Start by sceduling all of the bottom-most times
# Then recursively call all available dependencies
sub alap {
    my $obj = shift;
    my @recurse = ();
    $obj->{alap}->{op_cnt} = 0;
    foreach my $op_ref ( keys $obj->{bottom} ) {
        my $op = $obj->{bottom}->{$op_ref};
        $op->{alap}->{sched} = 1;
        $op->{alap}->{ts} = $obj->{length} - $op->{length};
        $op->{slack} = $op->{alap}->{ts} - $op->{asap}->{ts};
        print "W: $op->{text} has negative slack $op->{slack} (ASAP: $op->{asap}->{ts}, ALAP: $op->{alap}->{ts})\n" if ( $op->{slack} < 0 );
        push @{ $obj->{alap}->{$op->{alap}->{ts}} }, $op;
        $obj->{alap}->{threads} = ::max ($obj->{alap}->{threads}, scalar @{ $obj->{alap}->{$op->{alap}->{ts}} });
        $obj->{alap}->{op_cnt}++;
        foreach my $pre ( @{ $op->{in_edges} } ) {
            my $next = 1;
            foreach my $dep ( @{ $pre->{out_edges} } ) {
                $next &= $dep->{alap}->{sched};
            }
            if ( $next ) {
                $pre->{alap}->{ts} = $obj->{length};
                # Search preferred to hash because most lists will be short 
                # (<3 elements, max <20)
                push(@recurse, $pre) unless grep{$_ == $pre} @recurse;
            }
        }
    }
    $obj->alap_recurse("alap",@recurse);
    if ($obj->{op_cnt} != $obj->{alap}->{op_cnt}) {
        print  "W: $obj->{function} op counts mismatch (Sched: $obj->{op_cnt}, ALAP: $obj->{alap}->{op_cnt})\n";
    }
}

sub alap_recurse {
    my $obj = shift;
    my $type = shift;
    my @recurse = ();
    foreach my $op ( @_ ) {
        $op->{$type}->{sched} = 1;
        foreach my $dep ( @{ $op->{out_edges} } ) {
            $op->{$type}->{ts} = min ($op->{$type}->{ts}, $dep->{$type}->{ts} - $op->{length});
        }
        if ($type eq "alap") {
            $op->{slack} = $op->{alap}->{ts} - $op->{asap}->{ts};
            print "W: $op->{text} has negative slack $op->{slack} (ASAP: $op->{asap}->{ts}, ALAP: $op->{alap}->{ts})\n" if ( $op->{slack} < 0 );
        }
        push @{ $obj->{$type}->{$op->{$type}->{ts}} }, $op;
        $obj->{$type}->{threads} = ::max ($obj->{$type}->{threads}, scalar @{ $obj->{$type}->{$op->{$type}->{ts}} });
        $obj->{$type}->{op_cnt}++;
        foreach my $pre ( @{ $op->{in_edges} } ) {
            my $next = 1;
            foreach my $dep ( @{ $pre->{out_edges} } ) {
                $next &= $dep->{$type}->{sched};
            }
            if ( $next ) {
                $pre->{$type}->{ts} = $obj->{length};
                # Search preferred to hash because most lists will be short (<3 elements)
                push(@recurse, $pre) unless grep{$_ == $pre} @recurse;
            }
        }
    }
    $obj->alap_recurse($type,@recurse)    if ( scalar @recurse );
}

# As Centered As Possble scheduling
sub acap {
    my $obj = shift;
    my $type = "acap";
    my $mid = $obj->{length} >> 1;  # divide by two, without decimal
    my @recurse = ();
    # Copy second half of ASAP schedule to end of ACAP schedule
    for (my $ts=$mid; $ts < $obj->{length}; $ts++) {
        foreach my $op ( @{ $obj->{asap}->{$ts} } ) {
            push @{ $obj->{$type}->{$ts} }, $op;
            $op->{$type}->{ts} = $ts;
            $op->{$type}->{sched} = 1;
            $obj->{$type}->{threads} = ::max ($obj->{$type}->{threads}, scalar @{ $obj->{$type}->{$ts} });
            $obj->{$type}->{op_cnt}++;
            # Build recursive list of next operations
            foreach my $pre ( @{ $op->{in_edges} } ) {
                if ( ! $pre->{acap}->{sched} ) {
                    my $next = 1;
                    foreach my $dep ( @{ $pre->{out_edges} } ) {
                        $next &= $dep->{$type}->{sched};
                    }
                    if ( $next ) {
                        $pre->{$type}->{ts} = $obj->{length};
                        # Search preferred to hash because most lists will be short 
                        # (<3 elements, max <20) and hashes are expensive in memory
                        push(@recurse, $pre) unless grep{$_ == $pre} @recurse;
                    }
                }
            }
        }
    }
    $obj->alap_recurse($type,@recurse)    if ( scalar @recurse );
}

# LPFS Longest Path First Scheduling. Performs a breadth first search to 
# find the longest path(s) in the graph and assign those directly to SIMD 
# regions. All other operations are scheduled to remaining SIMD regions 
# as available.
sub lpfs {
    my $obj = shift;
    my $ts = 0;
    my $op_cnt = 0; # only ops, no moves
    my $tgate = 0; # set if T/Tdag found
    my $t_cnt = 0; # count of timesteps with a T/Tdag
    my $width = 0;
    my $ready = {};
    my $simds = {};
    my $pathsearch = 1;    # 0 if all paths are discovered
    my $simd_l = $obj->{lpfs}->{simd_l};
    my $opp_simd = $obj->{lpfs}->{opp_simd};
    my $refill = $obj->{lpfs}->{refill_simd};
    # Find longest paths
    for ( my $i=1; $pathsearch and $i<=$simd_l; $i++ ) {
        @{ $simds->{$i} } = $obj->find_lp( $obj->{top} );
        $pathsearch = 0    if ( ! @{ $simds->{$i} } );
    }
    $ready = $obj->lpfs_init();
    while ( ( keys %$ready ) or $op_cnt < $obj->{op_cnt} ) {
        $tgate = 0;
        if ( $ts > $obj->{op_cnt} ) {
            print "E: LPFS timestep $ts > op count $obj->{op_cnt}. Aborting.\n";
            return;
        }
        my $next = {};
        # Schedule all assigned paths if ready
        foreach my $simd ( 1..$simd_l ) {
            if ( $pathsearch and $refill and ! scalar @{ $simds->{$simd} } ) {
                @{ $simds->{$simd} } = $obj->find_lp( ref values $ready );
                $pathsearch = 0    if ( ! @{ $simds->{$simd} } );
            }
            my $op = $simds->{$simd}->[0];
            if ( $obj->sched_op( "lpfs", $op, $ts, $simd ) ) {
                shift @{ $simds->{$simd} };
                $width = ($width,$simd)[$width < $simd];
                $op_cnt++;
                $tgate |= ( $op->{op} =~ /^(?:T|Tdag)$/ );
                # add opportunistic scheduling to a scehduled simd region already doing the same op
                if ( $opp_simd ) {
                    foreach ( $obj->lpfs_extract_optype( "lpfs", $op->{op}, $ready ) ) {
                        $obj->sched_op( "lpfs", $_, $ts, $simd );
                        $op_cnt++;
                    }
                }
            }
        }
        # Schedule any remaing ready tasks
        for ( my $simd=$simd_l+1; $simd<=$obj->{lpfs}->{simd_k}; $simd++ ) {
            # Remove all longest path ops from the front
            my $scheduled = 0;
            while ( ! $scheduled and ( keys $ready ) ) {
                my $id = 0;
                while ( $id = ::min(keys %$ready) and $ready->{$id}->{followed} ) {
                    print "W: followed!\n";
                    delete $ready->{$id};
                }
                if ( $opp_simd ) {
                    foreach ( $obj->lpfs_extract_optype( "lpfs", $ready->{$id}->{op}, $ready ) ) {
                        $obj->sched_op( "lpfs", $_, $ts, $simd );
                        $width = ($width,$simd)[$width < $simd];
                        $scheduled = 1;
                        $op_cnt++;
                        $tgate |= ( $_->{op} =~ /^(?:T|Tdag)$/ );
                    }
                } else {
                    $scheduled = $obj->sched_op( "lpfs", $ready->{$id}, $ts, $simd );
                    if ( $scheduled ) {
                        $op_cnt++;
                        $tgate |= ( $ready->{$id}->{op} =~ /^(?:T|Tdag)$/ );
                        delete $ready->{$id};
                        $width = ($width,$simd)[$width < $simd];
                    }
                }
            }
        }
        # Update data moves
        $obj->{lpfs}->{$ts}->{0} = $obj->update_moves("lpfs", $ts);
        # Update ready list
        $ready = $obj->lpfs_update_ready( $ts, $ready );
        $ts++;
        $t_cnt += $tgate;
    }
    print "E: ops mis-scheduled ($op_cnt out of $obj->{op_cnt})\n"   if ( $op_cnt != $obj->{op_cnt} );
    $obj->{lpfs}->{len} = $ts;
    $obj->{lpfs}->{tgates} = $t_cnt;
    $obj->{lpfs}->{width} = $width;
}

sub lpfs_init {
    my $obj = shift;
    my $ready = {};
    $obj->{lpfs}->{op_cnt} = 0;
    foreach $op ( @{ $obj->{top} } ) {
        $ready->{$op->{id}} = $op   if ( ! $op->{lpfs}->{followed} );
    }
    foreach my $op ( @{ $obj->{ops} } ) {
        $op->{lpfs}->{ts}    = -1;
        $op->{lpfs}->{simd}  = -1;
        $op->{lpfs}->{dist}  = 0;
        $op->{lpfs}->{tag}   = 0;
    }
    while ( my ($q, $qubit) = each %{ $obj->{qubits} } ) {
        $qubit->{loc}       = 0;
        $qubit->{last_op}   = -1;
    }
    return $ready;
}

sub lpfs_update_ready {
    my $obj = shift;
    my $ts = shift;
    my $ready = shift;
    my $next = {};
    foreach my $simd ( keys %{ $obj->{lpfs}->{$ts} } ) {
        if ( $simd != 0 ) {
            foreach my $op ( @{ $obj->{lpfs}->{$ts}->{$simd} } ) {
                foreach my $child ( $op->get_ready_children("lpfs") ) {
                    $ready->{$child->{id}} = $child    if ( ! $child->{lpfs}->{followed} );
                }
            }
        }
    }
    return $ready;
}

sub lpfs_extract_optype {
    my $obj = shift;
    my $sched = shift;
    my $optype = shift;
    my $ready = shift;
    my @ret = ();
    my $cnt = 0;
    foreach $id ( keys %$ready ) {
        if ( $ready->{$id}->{op} eq $optype and $cnt < $obj->{$sched}->{simd_d} ) {
            push @ret, $ready->{$id};
            delete $ready->{$id};
            $cnt++;
        }
    }
    return @ret;
}


sub find_lp {
    my $obj = shift;
    my $top = shift;
    return ()   if ( ! @$top );
    my @path = ();
    my $id = 0x7fffffff;
    my $op = ();
    foreach my $op ( @{ $obj->{ops} } ) {
        $op->{lpfs}->{dist} = 1     if ( ! $op->{lpfs}->{followed} );
    };
    foreach $op ( @$top ) {
        $id = ($id, $op->{id})[$id > $op->{id}];
    };
    foreach $op ( @{ $obj->{ops} } ) {
        if ( $op->{id} >= $id ) {
            if ( $op->{lpfs}->{followed} ) {
                $op->{lpfs}->{dist} = 0;
            } else {
                foreach my $child ( @{ $op->{out_edges} } ) {
                    my $dist = $op->{lpfs}->{dist} + 1;
                    $child->{lpfs}->{dist} = ($dist, $child->{lpfs}->{dist})[$dist < $child->{lpfs}->{dist}]
                }
            }
            $path[0] = $op  if ( ! $op->{lpfs}->{followed} 
                                 and $op->{lpfs}->{dist} > $path[0]->{lpfs}->{dist} );
        }
    }
    return  if ( ! @path );
    PATH: while ( $path[0]->{lpfs}->{dist} > 1 ) {
        my $dist = $path[0]->{lpfs}->{dist} - 1;
        $path[0]->take_path( $obj->{lpfs}->{path} );
        foreach my $parent ( @{ $path[0]->{in_edges} } ) {
            if ( $parent->{lpfs}->{dist} == $dist ) {
                unshift @path, $parent;
                next PATH;
            }
        }
        print "E: unable to find path from $path[0]->{text} (dist: $path[0]->{lpfs}->{dist})\n";
    }
    $path[0]->take_path( $obj->{lpfs}->{path} );
    $obj->{lpfs}->{path}++;
    return @path;
}

sub find_lp_orig {
    my $obj = shift;
    my $top = shift;
    return ()   if ( ! @$top );
    my $next = ();
    my $curr = ();
    my $tag = 0;
    my @path = ();
    my $ops = 0;
    my $children = 0;
    my $parents = 0;
    my $acc = 0;
    my $maxfan = 0;
    @$next = @$top; # copy list instead of modifying
    # Reset all distances before recomputing
    foreach my $op ( @{ $obj->{ops} } ) {
        $op->{lpfs}->{dist} = 1     if ( ! $op->{lpfs}->{followed} );
        $op->{lpfs}->{tag} = 0;
    };
    while ( (my $len = scalar @$next) > 0 ) {
        $acc += $len;
        $max_fan = ($max_fan, $len)[$max_fan < $len];
        $tag++;
        $curr = $next;
        $next = ();
        foreach my $op ( @{ $curr } ) {
            $ops++;
            my $dist = $op->{lpfs}->{dist} + 1;
            foreach my $child ( @{ $op->{out_edges} } ) {
                $children++;
                if ( ! $child->{lpfs}->{followed} ) {
                    $child->{lpfs}->{dist} = ($dist, $child->{lpfs}->{dist})[$dist < $child->{lpfs}->{dist}];
                }
                if ( ! $child->{lpfs}->{tag} ) {
                    $child->{lpfs}->{tag} = $tag;
                    push @$next, $child;
                }
            }
            $path[0] = $op  if ( ! $op->{lpfs}->{followed} 
                                 and $op->{lpfs}->{dist} > $path[0]->{lpfs}->{dist} );
        }
        print "Total ops: $obj->{op_cnt}; visited ops: $ops; children: $children; parents: $parents\n";
        print "Levels: $tag; avg: ".($acc/$tag)."; max: $max_fan\n";
    }
    return  if ( ! @path );
    PATH: while ( $path[0]->{lpfs}->{dist} > 1 ) {
        my $dist = $path[0]->{lpfs}->{dist} - 1;
        $path[0]->take_path( $obj->{lpfs}->{path} );
        foreach my $parent ( @{ $path[0]->{in_edges} } ) {
            $parents++;
            if ( $parent->{lpfs}->{dist} == $dist ) {
                unshift @path, $parent;
                $dist--;
                next PATH;
            }
        }
        print "E: unable to find path from $path[0]->{text} (dist: $path[0]->{lpfs}->{dist})\n";
    }
    if ( $::DEBUG > 20 ) {
        print "Total ops: $obj->{op_cnt}; visited ops: $ops; children: $children; parents: $parents\n";
        print "Levels: $tag; avg: ".($acc/$tag)."; max: $max_fan\n";
    }
    $path[0]->take_path( $obj->{lpfs}->{path} );
    $obj->{lpfs}->{path}++;
    return @path;
}

sub sched_op {
    my $obj = shift;
    my $sched = shift;
    my $op = shift;
    my $ts = shift;
    my $simd = shift;
    if ( defined $op and $op->op_ready($sched) ) {
        push @{ $obj->{$sched}->{$ts}->{$simd} }, $op;
        $obj->{$sched}->{op_cnt}++;
        $op->{$sched}->{simd} = $simd;
        $op->{$sched}->{ts} = $ts;
        $op->{$sched}->{followed} = 1   if ( $sched eq "lpfs" );
        return 1;
    }
    return 0;
}

sub extract_optype {
    my $obj = shift;
    my $sched = shift;
    my $optype = shift;
    my $ready = shift;
    my $i = 0;
    # Sort the ready list by optype and scan for start and end of desired optype
    @$ready = sort {$a->{op} cmp $b->{op}} @$ready;
    while ( $i < scalar @$ready and $ready->[$i]->{op} ne $optype ) { $i++; }
    return  if ( $i >= scalar @$ready );
    my $s = $i;
    # Make sure that we don't schedule too many ops to the SIMD region
    my $size = scalar @{ $ready->[$i]->{args} };
    my $cnt = $size;
    while ( $i < scalar @$ready 
            and $ready->[$i]->{op} eq $optype 
            and $cnt+$size < $obj->{$sched}->{simd_d} ) {
        $i++;
        $cnt += $size;
    }
    my $l = $i - $s;
    return splice @$ready, $s, $l;
}

sub update_moves {
    my $obj = shift;
    my $sched = shift;
    my $ts = shift;
    my $s = $obj->{$sched};
    my $simd = 1;
    my $op = ();
    my $qubit = ();
    my $moves = ();
    my $active = $obj->{active_qubits};
    my $curr = {};
    my $next = {};
    my @simd_active = (0) x ($s->{simd_k}+1);
    # Get all current 
    foreach $simd ( 1..$s->{simd_k} ) {
        $simd_active[$simd] = 1  if ( scalar @{ $s->{$ts}->{$simd} } );
        foreach $op ( @{ $s->{$ts}->{$simd} } ) {
            foreach $qubit ( @{ $op->{args} } ) {
                $curr->{$qubit->{name}} = $simd;
            }
        }
    }
    foreach my $name ( keys %$active ) {
        my $src = $active->{$name};
        my $dst = $curr->{$name} || 0;
        if ( $dst ) {
            # qubit is reused, keep it
            $next->{$name} = $dst;
            delete $curr->{$name};  # remove for fetching
        }
        if ( ! $dst and ! $simd_active[ $src ] ) {
            # Qubit doesn't need to move
            $next->{$name} = $src;
        }
        if ( ( ( $dst ) and $dst != $src )
             or ( ( ! $dst ) and $simd_active[ $src ] ) ) {
            # Moved into new location or SIMD is active and need to move to memory
            $qubit = $obj->{qubits}->{$name};
            push @$moves, bless({
                'op'    => 'MOV',
                'src'   => $src,
                'dst'   => $dst,
                'text'  => "MOV $dst $src $name",
                'args'  => [$qubit]
            },'Op');
            $qubit->{loc} = $dst;
            $s->{op_cnt}++;
            $s->{moves}++;
        }
    }
    foreach my $name ( keys %$curr ) {
        # Qubit not active, fetch from mem
        my $dst = $curr->{$name};
        $next->{$name} = $dst;
        $qubit = $obj->{qubits}->{$name};
        push @$moves, bless({
            'op'    => 'MOV',
            'src'   => 0,
            'dst'   => $dst,
            'text'  => "MOV $dst 0 $name",
            'args'  => [$qubit]
        },'Op');
        $qubit->{loc} = $dst;  # Qubit moved to SIMD
        $s->{op_cnt}++;
        $s->{moves}++;
    }
    $s->{mts}++     if ( @$moves );
    $obj->{active_qubits} = $next;
    @$moves = sort { $a->{text} cmp $b->{text} } @$moves;
    return $moves;
}

sub update_moves_hold_for_stall {
    my $obj = shift;
    my $sched = shift;
    my $ts = shift;
    my $s = $obj->{$sched};
    my $simd = 1;
    my $op = ();
    my $qubit = ();
    my $moves = ();
    my $curr = {};
    my $prev = {};
    foreach $simd ( 1..$s->{simd_k} ) {
        my $pts = $ts-1;
        # Find previous non-empty timestep for this simd region
        while ( $pts >= 0 and ! scalar @{ $s->{$pts}->{$simd} } ) { $pts--; }
        # get all previous qubits
        if ( $pts >= 0 ) {
            foreach $op ( @{ $s->{$pts}->{$simd} } ) {
                foreach $qubit ( @{ $op->{args} } ) {
                    $prev->{$qubit->{name}} = $qubit->{loc};
                }
            }
        }
        # get all current qubits
        foreach $op ( @{ $s->{$ts}->{$simd} } ) {
            foreach $qubit ( @{ $op->{args} } ) {
                $curr->{$qubit->{name}} = [ $simd, $qubit->{loc} ]; #dst, src
            }
        }
    }
    foreach my $name ( sort keys %$curr ) {
        my $src = $curr->{$name}->[1];
        my $dst = $curr->{$name}->[0];
        $qubit = $obj->{qubits}->{$name};
        if ( $src != $dst ) {
            push @$moves, bless({
                'op'    => 'MOV',
                'src'   => $src,
                'dst'   => $dst,
                'args'  => ($qubit),
                'text'  => "MOV $dst $src $name",
                'args'  => [$qubit]
            },'Op');
            $qubit->{loc} = $dst;  # Qubit moved to SIMD
            $obj->{$sched}->{op_cnt}++;
            $obj->{$sched}->{moves}++;
        }
        delete $prev->{$name}   if ( exists $prev->{$name} );
    }
    foreach my $name ( sort {$b cmp $a} keys %$prev ) {
        my $src = $prev->{$name};
        my $dst = 0;
        $qubit = $obj->{qubits}->{$name};
        if ( @{ $s->{$ts}->{$src} } ) {
            push @$moves, bless({
                'op'    => 'MOV',
                'src'   => $src,
                'dst'   => $dst,
                'args'  => ($qubit),
                'text'  => "MOV $dst $src $name",
                'args'  => [$qubit]
            },'Op');
            $qubit->{loc} = $dst;  # Qubit moved to SIMD
            $obj->{$sched}->{op_cnt}++;
            $obj->{$sched}->{moves}++;
        }
    }
    $obj->{$sched}->{mts}++     if ( @$moves );
    @$moves = sort { $a->{text} cmp $b->{text} } @$moves;
    return $moves;
}

sub update_moves_orig {
    my $obj = shift;
    my $sched = shift;
    my $ts = shift;
    my $moves = ();
    my $prev = {};
    # Get qubits from previous timestep
    if ( $ts > 0 ) {
        foreach my $simd ( sort keys %{ $obj->{$sched}->{$ts-1} } ) {
            if ( $simd > 0 and scalar @{ $obj->{$sched}->{$ts}->{$simd} } ) {
                foreach my $op ( @{ $obj->{$sched}->{$ts-1}->{$simd} } ) {
                    foreach my $qubit ( @{ $op->{args} } ) {
                        $prev->{$qubit->{name}} = $qubit;
                    }
                }
            }
        }
    }
    foreach my $simd ( sort keys %{ $obj->{$sched}->{$ts} } ) {
        foreach my $op ( @{ $obj->{$sched}->{$ts}->{$simd} } ) {
            foreach my $qubit ( @{ $op->{args} } ) {
                delete $prev->{$qubit->{name}}; # remove if used in previous timestep
                if ( $qubit->{loc} == -1 ) {
                    #print STDERR "alloc\n";
                    $qubit->{loc} = $simd;  # Qubit allocated to first SIMD use
                } elsif ( $qubit->{loc} != $simd ) {
                    #print STDERR "moved#\n";
                    push @$moves, {
                        'op'    => 'MOV',
                        'src'   => $qubit->{loc},
                        'dst'   => $simd,
                        'args'  => ($qubit),
                        'text'  => "MOV $simd $qubit->{loc} $qubit->{name}",
                        'args'  => [$qubit]
                    };
                    $qubit->{loc} = $simd;  # Qubit moved to SIMD
                    $obj->{$sched}->{op_cnt}++;
                    $obj->{$sched}->{moves}++;
                }
            }
        }
    }
    # Any remaining qubits from previous timestep should be moved to memory
    foreach my $q ( keys %$prev ) {
        my $qubit = $prev->{$q};
        push @$moves, {
            'text'  => "MOV 0 $qubit->{loc} $qubit->{name}",
            'args'  => [$qubit]
        };
        $qubit->{loc} = 0;  # Qubit moved to memory
        $obj->{$sched}->{op_cnt}++;
        $obj->{$sched}->{moves}++;
    }
    $obj->{$sched}->{mts}++     if ( @$moves );
    return $moves;
}

sub print_rcpq {
    my $obj = shift;
    foreach my $op ( @{ $obj->{rcp}->{rcpq} } ) {
        print "$op->{text}\n";
    }
}

sub rcp {
    my $obj = shift;
    my $s = $obj->{rcp};
    my $ts = 0;
    my $op_cnt = 0; # Track gates ops (not moves) scheduled
    my %simds = ();
    my $tgate = 0;
    my $t_cnt = 0;
    my $rcpq = $obj->rcp_init();
    while ( ( keys %{ $rcpq } ) && $op_cnt < $obj->{op_cnt} ) {
        $tgate = 0;
        if ( $ts > $obj->{op_cnt} ) {
            print "E: RCP timestep $ts > op count $obj->{op_cnt}. Aborting.\n";
            return;
        }
        #if ( $::DEBUG > 50 ) {
        #    print "\nRCPQ @ $ts\n-------------\n";
        #    $obj->print_rcpq();
        #}
        foreach ( 1..$s->{simd_k} ) { $simds{$_} = 1; }
        while ( scalar keys %{ $rcpq } && scalar keys %simds ) {
            my $simd = $obj->rcp_sched_next_simd( $rcpq, $ts, keys %simds );
            $op_cnt += scalar @{ $s->{$ts}->{$simd} };
            $tgate |= ( $s->{$ts}->{$simd}->[0]->{op} =~ /^(?:T|Tdag)$/ );
            delete $simds{$simd};
        }
        print "E: Scheduled too many ops ($op_cnt instead of $obj->{op_cnt})\n" if ( $op_cnt > $obj->{op_cnt} );
        $s->{$ts}->{0} = $obj->update_moves( "rcp", $ts );
        $rcpq = $obj->rcpq_update( $ts, $rcpq );
        $ts++;
        $t_cnt += $tgate
    }
    $s->{len} = $ts;
    $s->{width} = $obj->{rcp}->{simd_k};
    $s->{tgates} = $t_cnt;
}

# Initialize RCPQ
# Update: RCPQ
sub rcp_init {
    my $obj = shift;
    my $s = $obj->{rcp};
    my $rcpq = ();
    # copy top to RCPQ
    $s->{op_cnt} = 0;
    foreach my $op ( @{ $obj->{top} } ) {
        $rcpq->{$op->{id}} = $op;
    }
    foreach my $op ( @{ $obj->{ops} } ) {
        $s->{ts}    = -1;
        $s->{simd}  = -1;
    }
    while ( my ($q, $qubit) = each %{ $obj->{qubits} } ) {
        $qubit->{loc}       = 0;
        $qubit->{last_op}   = -1;
    }
    return $rcpq;
}

# Calculate all weights in the RCPQ
# Input: available SIMD regions
# Return: \%{$simd} = {$max, $op, %weights{op} = $weight}
sub rcp_calc_weights {
    my $obj = shift;
    my $rcpq = shift;
    my $ret = {};
    # init $ret
    foreach my $simd (@_) {
        $ret->{$simd} = {
                            max => -2**63,  # set to very low value
                            op  => ''
                        };
    }
    foreach my $op ( values %$rcpq ) {
        my $tmp = $obj->rcp_op_weight( $op, @_ );
        while ( my ($simd, $weight) = each %$tmp ) {
            my $tmp = $ret->{$simd};
            $tmp->{ $op->{op} } += $weight;
            if ( $tmp->{ $op->{op} } > $tmp->{max} ) {
                $tmp->{max} = $tmp->{ $op->{op} };
                $tmp->{op} = $op->{op};
            }
        }
    }
    return $ret;
}

# Calculate operation weight for a simd region
# Inputs: $op = Op, @simd = available SIMD regions
# Return: $weight
sub rcp_op_weight {
    my $obj = shift;
    my $op = shift;
    my $w_op = $obj->{rcp}->{w_op};
    my $w_slack = $obj->{rcp}->{w_slack};
    my $w_dist = $obj->{rcp}->{w_dist};
    my $ret = {};
    foreach my $simd (@_) {
            # Weight for the communication distance
            my $dist = 1;
            foreach my $qubit ( @{ $op->{args} } ) {
                # Check that all qubits are in the current simd region
                $dist &= ($qubit->{loc} == -1 || $qubit->{loc} == $simd);
            }
            my $weight = $w_op +
                         $w_slack * $op->{slack} +
                         $w_dist * $dist;
            #$op->{rcp}->{simd_weight}->{$simd} = $weight;
            $ret->{$simd} = $weight;
    }
    return $ret;
}

# Get the next highest SIMD weight
# Input: timestep
# Side-effect: Schedule next highest SIMD region to timestep
#   $obj->{rcp}->{$ts}->{$simd} is populated
# Return: SIMD region populated
sub rcp_sched_next_simd {
    my $obj = shift;
    my $rcpq = shift;
    my $ts = shift;
    my $weights = $obj->rcp_calc_weights( $rcpq, @_ );
    # Sort by highest weight, then by lowest simd_k region
    my $simd = (sort { $weights->{a}->{max} <=> $weights->{b}->{max} || $a <=> $b } keys %$weights )[0];
    print "E: $simd already scheduled at timestep $ts!\n"  if ( @{ $obj->{rcp}->{$ts}->{$simd} } );
    foreach my $op ( $obj->rcpq_extract_optype( $rcpq, $weights->{$simd}->{op} ) ) {
        $obj->sched_op( "rcp", $op, $ts, $simd );
    }
    return $simd;
}

# Gets all operations of the requested type from the RCPQ and removes matches 
# from RCPQ
# Input: $optype
# Update: RCPQ
# Return: \@ = [Op]
sub rcpq_extract_optype {
    my $obj = shift;
    my $rcpq = shift;
    my $optype = shift;
    my $i = 0;
    my $size = 0;
    my $simd_d = $obj->{rcp}->{simd_d};
    my @ret = ();
    while ( ( my ($id, $op) = each %$rcpq ) and
            $size * ( 1 + scalar @ret ) < $simd_d ) {
        if ( $op->{op} eq $optype ) {
            $size |= scalar @{ $op->{args} };
            push @ret, $op;
            delete $rcpq->{$id};
        }
    }
    return @ret;
    # Sort the RCPQ by optype and scan for start and end of desired optype
    #@{$obj->{rcp}->{rcpq}} = sort {$a->{op} cmp $b->{op}} @{ $obj->{rcp}->{rcpq} };
    #while ( $i < scalar @{$obj->{rcp}->{rcpq}} and $obj->{rcp}->{rcpq}->[$i]->{op} ne $optype ) { $i++; }
    #my $s = $i;
    ## Make sure that we don't schedule too many ops to the SIMD region
    #my $size = scalar @{ $obj->{rcp}->{rcpq}->[$i]->{args} };
    #my $cnt = $size;
    #while ( $i < scalar @{$obj->{rcp}->{rcpq}} 
    #        and $obj->{rcp}->{rcpq}->[$i]->{op} eq $optype 
    #        and $cnt+$size < $obj->{rcp}->{simd_d} ) {
    #    $i++;
    #    $cnt += $size;
    #}
    #my $l = $i - $s;
    #return splice @{$obj->{rcp}->{rcpq}}, $s, $l;
}

# Checks all scheduled ops and adds them to the RCPQ
# Input: $ts, $rcpq pointer
# Update: RCPQ
# Return: null
sub rcpq_update {
    my $obj = shift;
    my $ts = shift;
    my $rcpq = shift;
    my $s = $obj->{rcp};
    my $simd_k = $s->{simd_k};
    foreach my $simd ( 1..$simd_k ) {
        foreach my $op ( @{ $s->{$ts}->{$simd} } ) {
            foreach my $child ( $op->get_ready_children("rcp") ) {
                $rcpq->{$child->{id}} = $child;
            }
        }
    }
    return $rcpq;
}

# Determine fetches from memory and other simd regions
# Input: current timestep and SIMD region
# Side-effect: propagates scheduling to operations
# Output: @[Qubit] = all qubits moving to SIMD region
sub rcp_simd_fetch {
    my $obj = shift;
    my $ts = shift;
    my $simd = shift;
    my @qubits = ();
    foreach my $op ( @{ $obj->{rcp}->{$ts}->{$simd} } ) {
        push @qubits, $op->rcp_fetch_op( $ts, $simd );
    }
    return @qubits;
}

# Determine stores to memory
# NOTE: must be run after rcp_simd_fetch - depends on scheduling information 
#       for current cycle
# Input: current timestep
# Output: @[Qubit] = all qubits moving out of SIMD region
sub rcp_simd_store {
    my $obj = shift;
    my $ts = shift;
    my $simd = shift;
    my @qubits = ();
    # look at the previous cycle's ops in the simd region and remove the ones
    # that aren't scheduled for this cycle
    $ts--;
    if ( exists $obj->{rcp}->{$ts} ) {
        foreach my $op ( @{ $obj->{rcp}->{$ts}->{$simd} } ) {
            push @qubits, $op->rcp_store_op( $ts );
        }
    }
    return @qubits;
}

sub ss_init {
    my $obj = shift;
    foreach my $op ( @{ $obj->{ops} } ) {
        $op->{ss}->{ts}     = -1;
        $op->{ss}->{simd}   = -1;
    }
    while ( my ($q, $qubit) = each %{ $obj->{qubits} } ) {
        $qubit->{loc}       = 0;
        $qubit->{last_op}   = -1;
    }
}

sub ss {
    my $obj = shift;
    my $ss = $obj->{ss};
    my $len = 0;
    my $width = 0;
    $obj->ss_init();
    foreach my $op ( @{ $obj->{ops} } ) {
        my $ts = $op->{rank}-1;
        my $simd = 1;
        my $done = 0;
        while ( ! $done and $simd <= $ss->{simd_k} ) {
            if ( ! @{ $ss->{$ts}->{$simd} } 
                 or ( scalar @{ $ss->{$ts}->{$simd} } < $ss->{simd_d}
                 and $ss->{$ts}->{$simd}->[0]->{op} eq $op->{op} ) ) {
                $done = $obj->sched_op( "ss", $op, $ts, $simd );;
                $width = ($width, $simd)[$width < $simd]    if ($done);
            }
            $simd++;
        }
        print "E: unable to place $op->{text} at $ts\n"     if ( ! $done );
        $len = ::max($len, $ts+1);
    }
    for ( my $ts=0; $ts < $len; $ts++ ) {
        $ss->{$ts}->{0} = $obj->update_moves("ss", $ts);
    }
    $ss->{len} = $len;
    $ss->{width} = $width;
    $obj->tgate_cnt("ss");
}

sub tgate_cnt {
    my $obj = shift;
    my $sched = shift;
    my $s = $obj->{$sched};
    my $tgate = 0;
    my $t_cnt = 0;
    foreach my $ts ( 0..( $s->{len}-1 ) ) {
        $tgate = 0;
        foreach my $simd ( 1..$s->{simd_k} ) {
            if ( defined $s->{$ts}->{$simd}->[0] and
                 $s->{$ts}->{$simd}->[0]->{op} =~ /^(?:T|Tdag)$/ ) {
                $tgate = 1;
            }
        }
        $t_cnt += $tgate;
    }
    $s->{tgates} = $t_cnt;
}

sub cpr {
    my $obj = shift;
    my $threshold = shift || 4;
    my @qconflicts = ();
    my @conflicts = ();
    my %stats = (
        'count'     => 0,
        'mean'      => 0,
        'median'    => 0,
        'mode'      => 0,
        'mode_cnt'  => 0,
        'hist'      => {},
        'var'       => 0,
        'min'       => 2147483647,
        'max'       => 0,
        'range'     => 0,
        'acc'       => 0,
        'raw'       => []
    );
    # Calculate cpr for each qubits' ops, collect intermediate stats on the way
    foreach my $qref ( keys %{ $obj->{qubits} } ) {
        my $qubit = $obj->{qubits}->{$qref};
        $qubit->{cpr}->[0] = 0;
        $qubit->{last_op} = 0;
        for ( my $i=1; $i<scalar @{ $qubit->{ops} }; $i++ ) {
            $qubit->{cpr}->[$i] = $qubit->{ops}->[$i]->{asap}->{ts} - $qubit->{ops}->[$i-1]->{asap}->{ts};
            $stats{acc} += $qubit->{cpr}->[$i];
            push @{ $stats{raw} }, $qubit->{cpr}->[$i];
            $stats{min} = ::min( $stats{min}, $qubit->{cpr}->[$i] );
            $stats{max} = ::max( $stats{max}, $qubit->{cpr}->[$i] );
            $stats{count}++;
        }
        if ( scalar @{$qubit->{cpr}} != scalar @{$qubit->{ops}} ) {
            printf "Warning: CPR count doesn't match op count (%d != %d)\n", scalar @{$qubit->{cpr}}, scalar @{$qubit->{ops}};
        }
    }
    # Calculate remaining stats
    if ( $stats{count} > 0 ) {
        $stats{mean} = $stats{acc} / $stats{count};
        $stats{median} = $stats{raw}->[int ( $stats{count}/2 + 0.5 )];
        foreach my $cpr ( @{ $stats{raw} } ) {
            $stats{var} += (abs ($cpr - $stats{mean}))**2;
            $stats{hist}->{$cpr}++;
            if ( $stats{hist}->{$cpr} > $stats{mode_cnt} ) {
                $stats{mode} = $cpr;
                $stats{mode_cnt} = $stats{hist}->{$cpr};
            }
        }
        $stats{var} /= $stats{count};
        $stats{range} = $stats{max} - $stats{min};
    }
    # Check ops for conflicts
    foreach my $ts ( sort {$a <=> $b} keys %{ $obj->{asap} } ) {
        if ( $ts =~ /^-?\d+$/ ) {
            foreach my $op ( @{ $obj->{asap}->{$ts} } ) {
                # If there are more than two arguments, check for a move conflict
                if ( scalar @{ $op->{args} } > 1 ) {
                    # Foreach qubit, check that the CPR value for the current operation is below the threshold and add it to @qconflict
                    foreach my $qubit ( @{ $op->{args} } ) {
                        my $idx = $qubit->{last_op};
                        if ( ::refaddr($op) == ::refaddr($qubit->{ops}->[$idx]) ) {
                            $qubit->{last_op}++;
                        } else {
                            # Start at next index and search through list - it's probably after the current one, not before
                            $idx++;
                            while ( $idx != $qubit->{last_op} && ::refaddr($op) != ::refaddr($qubit->{ops}->[$idx]) ) {
                                $idx = ($idx + 1) % scalar @{ $qubit->{ops} };
                            }
                            die "Qubit $qubit->{name} not found in operation '$op->{text}'\n"   if ( $idx == $qubit->{last_op} );
                            $qubit->{last_op} = $idx+1;
                        }
                        push @qconflicts, $qubit    if ( $qubit->{cpr}->[$idx] > 0 && $qubit->{cpr}->[$idx] <= $threshold );
                    }
                    # If $#qconflict > 0, add operation to @conflict
                    push @conflicts, $op    if ( scalar @qconflicts > 1 );
                }
                undef @qconflicts;  # free memory
            }
        }
    }
    # Print results
    my $msg = "$obj->{function}:";
    print "$msg\n";
    $msg =~ s/./-/g;
    print "$msg\n";
    foreach ( qw(mean median mode var range) ) { printf "%9s = %d\n", $_, $stats{$_}; }
    print "Histogram:\n";
    foreach ( sort {$a <=> $b} keys %{ $stats{hist} } ) { printf "%9s = %d\n", $_, $stats{hist}->{$_}; }
    printf "%s (%d / %d):\n", "conflicts", scalar @conflicts, $obj->{op_cnt};
    foreach my $op ( @conflicts ) { print "    $op->{text}\n"; }
    print "\n";
}

sub sched_check {
    my $obj = shift;
    my $type = shift || "asap";
    foreach my $ts ( sort keys %{ $obj->{$type} } ) {
        if ( $ts =~ /^-?\d+$/ ) {
            foreach my $op ( @{ $obj->{$type}->{$ts} } ) {
                foreach my $pre ( @{ $op->{in_edges} } ) {
                    print  "E: Dependency error; '$op->{text}'\@$op->{$type}->{ts}, not after '$pre->{text}'\@$pre->{$type}->{ts}\n"
                            if ( $pre->{$type}->{ts} >= $op->{$type}->{ts} );
                }
            }
        }
    }
}

sub header_print {
    my $obj = shift;
    my $sched = shift;
    my $msg = "Function: $obj->{function} ";
    $msg .= "(sched: $sched, op_cnt: $obj->{$sched}->{op_cnt}, ";
    $msg .= "k: $obj->{$sched}->{simd_k}, d: $obj->{$sched}->{simd_d}";
    $msg .= ", l: $obj->{$sched}->{simd_l}"           if ( $sched eq "lpfs" );
    $msg .= ", opp: $obj->{$sched}->{opp_simd}"       if ( $sched eq "lpfs" );
    $msg .= ", refill: $obj->{$sched}->{refill_simd}" if ( $sched eq "lpfs" );
    $msg .= ", w_op: $obj->{$sched}->{w_op}"          if ( $sched eq "rcp" );
    $msg .= ", w_dist: $obj->{$sched}->{w_dist}"      if ( $sched eq "rcp" );
    $msg .= ", w_slack: $obj->{$sched}->{w_slack}"    if ( $sched eq "rcp" );
    $msg .= ")";
    print "$msg\n";
    $msg =~ s/./=/g;
    print "$msg\n";
}

sub metrics_print {
    my $obj = shift;
    my $sched = shift;
    my $s = $obj->{$sched};
    my $k = $s->{simd_k};
    my $d = $s->{simd_d};
    my $w = $s->{width};
    my $ops = $s->{op_cnt} - $s->{moves};
    my $t1 = $obj->{op_cnt} * 2.0;
    my $w1 = $obj->{op_cnt} * 3.0;
    my $tinf = $obj->{length}; # inf means no comms.?
    my $tk = $s->{tk} = $s->{len} + 4*$s->{mts};
    my $wk = $s->{wk} = $s->{op_cnt};
    my $sk = $s->{sk} = $t1 / $tk;
    my $ek = $s->{ek} = $t1 / ($k * $tk);
    my $uk = $s->{uk} = $wk / ($k * $tk);
    my $qk = $s->{qk} = $t1**3 / ($k * $tk**2 * $wk);
    my $ck = $s->{ck} = 100.0 * $s->{moves} / $s->{op_cnt};
    my $sav = 200/3 - $ck;
    my $avg = $s->{avg} = ($s->{mts}) ? $s->{moves} / $s->{mts} : "inf";
    my $max = 0;
    my @moves = ();
    foreach my $ts ( 0..($s->{len} - 1) ) {
        push( @moves, scalar @{ $s->{$ts}->{0} } );
        $max = ( $max, $moves[-1] )[$max < $moves[-1]];
    }
    $s->{max} = $max;
    print "ops = $ops\n";
    print "moves = $s->{moves}\n";
    print "total = $s->{op_cnt}\n";
    print "ots = $s->{len}\n";
    print "mts = $s->{mts}\n";
    print "ts = $tk\n";
    print "SIMDs = $w\n";
    print "tgates = $s->{tgates}\n";
    print "T(1) = $t1\n";
    print "T(inf) = $tinf\n";
    print "T($k,$d) = $tk\n";
    print "Speedup = $sk\n";
    print "Efficiency = $ek\n";
    print "Utility = $uk\n";
    print "Quality = $uk\n";
    print "Overhead = $ck% (reduction: $sav)\n";
    print "Avg load = $avg\n";
    print "Peak load = $max\n";
    print "mlist = ".(join " ", @moves)."\n";
    print "\n";
}

sub sched_print {
    my $obj = shift;
    my $sched = shift;
    my $op_cnt = 0;
    my $i = 0;
    foreach my $ts ( 0..$obj->{$sched}->{len} ) {
        foreach my $simd ( 0..$obj->{$sched}->{simd_k} ) {
            foreach my $op ( sort { $a->{text} cmp $b->{text} } @{ $obj->{$sched}->{$ts}->{$simd} } ) {
                print "$ts,$simd $op->{text}\n";
                $op_cnt++;
            }
        }
    }
    print "E: Printed $op_cnt, expected $obj->{$sched}->{op_cnt}\n"  if ( $op_cnt != $obj->{$sched}->{op_cnt} );
    print "\n";
}

sub sched_pretty_print {
    my $obj = shift;
    my $sched = shift;
    my $op_cnt = 0;
    my $i = 0;
    my $cw = 1 + $obj->{twidth};
    my @ts = undef;
    if ( @_ ) {
        @ts = @_;
    } else {
        @ts = sort {$a <=> $b} keys %{ $obj->{$sched} };
    }
    my $at = ::max( 4, length sprintf "%d", $ts[-1] ); # 4 is length of string "Time"
    # Print column headings
    $msg = sprintf "%${at}s | %-${cw}s", "Time", "Moves";
    for ($i=1; $i<=$obj->{$sched}->{simd_k}; $i++) {
        $msg .= sprintf "| %-${cw}s", "SIMD $i";
    }
    print "$msg\n";
    $msg =~ s/./-/g;
    print "$msg\n";
    foreach my $time ( @ts ) {
        if ( $time =~ /^-?\d+$/ ) {
            my $rows = 0;
            my $ts = $time;
            # Determine number of rows that will be printed for this timestep
            for ($i=0; $i<=$obj->{$sched}->{simd_k}; $i++) {
                $rows = ::max( $rows, scalar @{$obj->{$sched}->{$ts}->{$i}} );
            }
            # Print each operation in the proper SIMD column
            for ($i=0; $i<$rows; $i++) {
                printf "%${at}s ", $time;
                $op_cnt++   if ( defined $obj->{$sched}->{$ts}->{0}->[$i] );
                printf "| %-${cw}s", $obj->{$sched}->{$ts}->{0}->[$i]->{text} || "";
                for (my $simd=1; $simd<=$obj->{$sched}->{simd_k}; $simd++) {
                    $op_cnt++   if ( defined $obj->{$sched}->{$ts}->{$simd}->[$i] );
                    printf "| %-${cw}s", $obj->{$sched}->{$ts}->{$simd}->[$i]->{text} || "";
                }
                print "\n";
                $time = ""; # only print $time on first row
            }
            print "$msg\n";
        }
    }
    print "E: Printed $op_cnt, expected $obj->{$sched}->{op_cnt}\n"  if ( $op_cnt != $obj->{$sched}->{op_cnt} );
    print "\n";
}

sub sched_true_print {
    my $obj = shift;
    my $sched = shift;
    my $s = $obj->{$sched};
    my $simd_k = $s->{simd_k};
    my $op = '';
    my $simd = 0;
    # real timestep - includes moves
    my $rts = 0;
    foreach my $fts ( 0..$s->{len} ) {
        my $curr = $s->{$fts};
        if ( exists $curr->{0} && scalar @{ $curr->{0} } ) {
            # Track all source and dest moves
            my @m_src = (0) x ( $simd_k + 1 );
            my @m_dst = (0) x ( $simd_k + 1 );
            foreach my $mov ( @{ $curr->{0} } ) {
                $m_src[$mov->{src}] = 1;
                $m_dst[$mov->{dst}] = 1;
            }
            # Print 3 timesteps where src is active (ignore memory SIMD)
            #foreach $op ( qw(H CNOT MEAS) ) {
            foreach $op ( qw(H CNOT) ) {
                foreach $simd ( 1..$simd_k ) {
                    print "$rts $simd MOV($op)\n"    if ( $m_src[$simd] );
                }
                $rts++;
            }
            # Print 2 timestep where dest is active
            foreach $op ( qw(X Z) ) {
                foreach $simd ( 1..$simd_k ) {
                    print "$rts $simd MOV($op)\n"    if ( $m_dst[$simd] );
                }
                $rts++;
            }
        }
        foreach $simd ( 1..$simd_k ) {
            # Print operation of the current SIMD for current timestep
            print "$rts $simd $curr->{$simd}->[0]->{op}\n"  if ( UNIVERSAL::isa( $curr->{$simd}->[0], "Op" ) );
        }
        $rts++;
    }
}

sub print {
    my $obj = shift;
    my $sched = shift || "asap";
    my $msg = "Function: $obj->{function} (op_cnt: $obj->{$sched}->{op_cnt}, max: $obj->{$sched}->{threads})";
    my @ts = sort {$a <=> $b} keys %{ $obj->{$sched} };
    my $op_cnt = 0;
    #print join " ", @ts;
    #print "\n";
    my $at = length sprintf "%d", $ts[-1];
    my $cw = 3 + $obj->{twidth};
    print "$msg\n";
    $msg =~ s/./=/g;
    print "$msg\n";
    foreach my $time ( @ts ) {
        if ( $time =~ /^-?\d+$/ ) {
            printf "%${at}d ", $time;
            @{$obj->{$sched}->{$time}} = sort { $a->{id} <=> $b->{id} } @{ $obj->{$sched}->{$time} };
            my $i=0;
            # Print operations
            foreach my $op ( @{$obj->{$sched}->{$time}} ) {
                printf "%-${cw}s", "| $op->{text}";
                $i++;
                $op_cnt++;
            }
            # Print formatting for unscheduled ops
            for (; $i<$obj->{$sched}->{threads}; $i++) {
                printf "%-${cw}s", "| "
            }
            print "\n";
        }
    }
    print "E: Printed $op_cnt, expected $obj->{$sched}->{op_cnt}\n"  if ( $op_cnt != $obj->{$sched}->{op_cnt} );
    print "\n";
}


package main;

use Getopt::Long;
use List::Util qw(max min);
use Scalar::Util qw(refaddr);
use Data::Dumper;
sub dm {$Data::Dumper::Maxdepth = shift || 3};

# Schedule parameters
$::DEBUG = 100;
$::SIMD_K = 4;
$::SIMD_D = 1024;
$::SIMD_L = 2;  # Number of SIMD regions allocated to longest paths
$::refill = 0;
$::opp = 0;
$::w_op = 1;
$::w_dist = -1;
$::w_slack = 1;
my $name = "lpfs";
my $metrics = 0;
my $schedule = 0;
my $pretty = 0;
my $true = 0;
my $dot = 0;
my $all = 0;
our %opts = (
    'asap'          => 0,
    'alap'          => 0,
    'acap'          => 0,
    'ss'            => 0,
    'rcp'           => 0,
    'lpfs'          => 0,
    'lpfs_debug'    => 0,
    'cpr'           => 0,
    'dot'           => 0,
    'leaves_only'   => 1,
    'conflict'      => 0
);

GetOptions ("d=i"       => \$::SIMD_D,
            "k=i"       => \$::SIMD_K,
            "l=i"       => \$::SIMD_L,
            "refill"    => \$::refill,
            "opp"       => \$::opp,
            "op:i"      => \$::w_op,
            "dist:i"    => \$::w_dist,
            "slack:i"   => \$::w_slack,
            "n=s"       => \$name,
            "g"         => \$dot,
            "m"         => \$metrics,
            "s"         => \$schedule,
            "p"         => \$pretty,
            "t"         => \$true,
            "a"         => \$all,
            "DEBUG=i"   => \$::DEBUG);
if ( $::SIMD_L >= $::SIMD_K ) {
    $::SIMD_L = $::SIMD_K >> 1; # half, rounded down
}
if ( $all ) { $metrics = 1; $schedule = 1; }
if ( ! ( $metrics or $schedule or $pretty or $dot or $true ) ) { $metrics = 1; }
if ( $::DEBUG >= 10 ) {
    print "M: \$::SIMD_K=$::SIMD_K; \$::SIMD_D=$::SIMD_D; \$::SIMD_L=$::SIMD_L\n";
}
if ( ! exists $opts{$name} ) {
    die "Invalid sched name $name!\n";
} else {
    $opts{$name} = 1;
}

our %func_sched = (
    'START'   => {'length' => 0},
    'END'     => {'length' => 0},
    'PrepZ'   => {'length' => 1},
    'MeasX'   => {'length' => 1},
    'MeasZ'   => {'length' => 1},
    'CNOT'    => {'length' => 1},
    'H'       => {'length' => 1},
    'S'       => {'length' => 1},
    'Sdag'    => {'length' => 1},
    'T'       => {'length' => 1},
    'Tdag'    => {'length' => 1},
    'X'       => {'length' => 1},
    'Y'       => {'length' => 1},
    'Z'       => {'length' => 1}
);
our @func_names = ();

sub max_key {
    my $hash = shift;
    my ($max, $ret) = each %$hash;
    while ( my ($k, $v) = each %$hash ) {
        if ( $max < $v ) {
            $max = $v;
            $ret = $k;
        }
    }
    return $ret;
}

sub main {
    my $file = $ARGV[0];
    my $function = '';

    open SCHED, $ARGV[0] or die "Unable to open file '$ARGV[0]': $!\n";

    while (<SCHED>) {
        chomp;
        if (/#Function (\w+)/) {
            $function = $1;
            push @func_names, $function;
            $func_sched{$function} = Schedule->new($function);

        } elsif (/#EndFunction/) {
            if ( $opts{asap} ) {
                print  "ASAP:\n";
                $func_sched{$function}->sched_check("asap");
                $func_sched{$function}->print("asap");
            }
            if ( $opts{alap} ) {
                print  "ALAP:\n";
                $func_sched{$function}->alap();
                $func_sched{$function}->sched_check("alap");
                $func_sched{$function}->print("alap");
            }
            if ( $opts{acap} ) {
                print  "ACAP:\n";
                $func_sched{$function}->acap();
                #print ::Dumper $func_sched{$function}->{acap};
                $func_sched{$function}->sched_check("acap");
                $func_sched{$function}->print("acap");
                $function = '';
            }
            if ( $opts{ss} ) {
                print  "SS:\n";
                $func_sched{$function}->ss();
                $func_sched{$function}->header_print("ss")          if ( $metrics or $schedule or $pretty or $true );
                $func_sched{$function}->metrics_print("ss")         if ( $metrics );
                $func_sched{$function}->sched_print("ss")           if ( $schedule );
                $func_sched{$function}->sched_pretty_print("ss")    if ( $pretty );
                $func_sched{$function}->sched_true_print("ss")      if ( $true );
            }
            if ( $opts{rcp} ) {
                print  "RCP:\n";
                $func_sched{$function}->rcp();
                $func_sched{$function}->header_print("rcp")         if ( $metrics or $schedule or $pretty or $true );
                $func_sched{$function}->metrics_print("rcp")        if ( $metrics );
                $func_sched{$function}->sched_print("rcp")          if ( $schedule );
                $func_sched{$function}->sched_pretty_print("rcp")   if ( $pretty );
                $func_sched{$function}->sched_true_print("rcp")     if ( $true );
            }
            if ( $opts{lpfs} ) {
                print  "LPFS:\n";
                $func_sched{$function}->lpfs();
                $func_sched{$function}->header_print("lpfs")        if ( $metrics or $schedule or $pretty or $true );
                $func_sched{$function}->metrics_print("lpfs")       if ( $metrics );
                $func_sched{$function}->sched_print("lpfs")         if ( $schedule );
                $func_sched{$function}->sched_pretty_print("lpfs")  if ( $pretty );
                $func_sched{$function}->sched_true_print("lpfs")    if ( $true );

            }
            if ( $opts{lpfs_debug} ) {
                my @path = $func_sched{$function}->find_lp( $func_sched{$function}->{top} );
	 	$func_sched{$function}->header_print("lpfs"); 
                while ( 3 < scalar @path ) {
                    foreach my $op ( @path ) {
                        print "$op->{dist} - $op->{text}\n";
                    }
		    @path = $func_sched{$function}->find_lp( $func_sched{$function}->{top} );
		    $func_sched{$function}->header_print("lpfs");
                }
                foreach my $op ( @path ) {
                    print "$op->{dist} - $op->{text}\n";
                }
                print "\n";
            }
            if ( $opts{cpr} ) {
                print  "Critical Path Report:\n";
                $func_sched{$function}->cpr();
            }
            if ( $opts{dot} ) {
                $func_sched{$function}->draw_graph("$ARGV[0]\.$function\.dot");
            }
            if ( $opts{conflict} ) {
            }
            if ( $opts{leaves_only} ) {
                # If only processing leaves, do not keep old functions
                delete $func_sched{$function};
            }
            #print "\n";

        # Check for defined function region, parse command, and verify that the command is defined
        } elsif ($function ne '' and /^\d+/ ) {
            my ($rank, $oper, @args) = split / /;
            if (! exists $func_sched{$oper}) {
                print  "Unknown command in function $function: $oper (line: $_)\n";
                next;
            }
            my $op = Op->new($oper, $rank, $func_sched{$oper}->{length});
            foreach my $arg (@args) {
                if ( ! exists($func_sched{$function}->{qubits}->{$arg}) ) {
                    $func_sched{$function}->{qubits}->{$arg} = Qubit->new($arg);
                }
                $func_sched{$function}->{qubits}->{$arg}->add_dep($op);
                $op->add_qubit($func_sched{$function}->{qubits}->{$arg});
            }
            $func_sched{$function}->add_op( $op );

        } elsif (/^#/ or /^\s*$/) {
            # comment or blank line, do nothing

        } else {
            print  "Unparsed line(func: $function, cmd: $1): $_\n";
        }
    }
    close SCHED;
}

dm();
main();

back to top