Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

Revision 0c99b1003662bd784e6c4d9d5053874918cc5ade authored by EPiQC on 08 July 2019, 21:28:06 UTC, committed by GitHub on 08 July 2019, 21:28:06 UTC
Merge pull request #38 from AndrewLitteken/master
Segmentation Faults and Fixes to Run main branch on macOS
2 parent s 98275a9 + 5ea8679
  • Files
  • Changes
  • 5e79c08
  • /
  • scripts
  • /
  • sched.pl
Raw File Download

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • revision
  • directory
  • content
revision badge
swh:1:rev:0c99b1003662bd784e6c4d9d5053874918cc5ade
directory badge
swh:1:dir:50473866082fc18b42173bdb06907d59a8383528
content badge
swh:1:cnt:c939b6de7b0ea40cafa37e164e4568b7753cd04d

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • revision
  • directory
  • content
(requires biblatex-software package)
Generating citation ...
(requires biblatex-software package)
Generating citation ...
(requires biblatex-software package)
Generating citation ...
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();

The diff you're trying to view is too large. Only the first 1000 changed files have been loaded.
Showing with 0 additions and 0 deletions (0 / 0 diffs computed)
swh spinner

Computing file changes ...

back to top

Software Heritage — Copyright (C) 2015–2026, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API