/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "dd_synthesis_p.hpp"

#include <iostream>
#include <map>
#include <vector>
#include <memory>

#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/assign/std/vector.hpp>
#include <boost/format.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/optional.hpp>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>

#include <puma.h>

#include <cudd.h>
#include <cuddInt.h>

#include <core/io/read_pla_to_bdd.hpp>

#include <reversible/circuit.hpp>
#include <reversible/functions/add_gates.hpp>
#include <reversible/functions/clear_circuit.hpp>

namespace cirkit
{

namespace internal
{

  using namespace boost::assign;

  typedef boost::graph_traits<dd>::vertex_descriptor dd_node;
  typedef boost::graph_traits<dd>::edge_descriptor   dd_edge;

  struct vertex_drawer
  {
    explicit vertex_drawer( const dd& graph ) : _graph ( graph ) {}

    template<typename Vertex>
    void operator()( std::ostream& os, const Vertex& v ) const
    {
      if ( !boost::in_degree( v, _graph ) || !boost::out_degree( v, _graph ) )
      {
        std::string label;
        if ( out_degree( v, _graph ) )
        {
          label = get_property( _graph, boost::graph_name ).labels.at( get( boost::vertex_name, _graph )[v].var );
        }
        else
        {
          label = boost::lexical_cast<std::string>( get( boost::vertex_name, _graph )[v].var );
        }
        os << "[shape=\"rectangle\",label=\"" << label << "\"]";
      }
      else
      {
        os << "[label=\""
           << get_property( _graph, boost::graph_name ).labels.at( get( boost::vertex_name, _graph )[v].var )
           << ":"
           << get( boost::vertex_name, _graph )[v].dtl
           << "\"]";
      }
    }

  private:
    const dd& _graph;
  };

  struct edge_drawer
  {
  public:
    explicit edge_drawer( const dd& graph ) : _graph( graph ) {}

    template<typename Edge>
    void operator()( std::ostream& os, const Edge& e ) const
    {
      dd_node source = boost::source( e, _graph );

      std::vector<std::string> properties;
      if ( e == *boost::out_edges( source, _graph ).first && in_degree( source, _graph ) )
      {
        properties += "style=dashed";
      }
      if ( get( boost::edge_name, _graph )[e].complemented )
      {
        properties += "color=red";
      }

      if ( properties.size() )
      {
        properties.front().insert( 0, "[" );
        properties.back().append( "]" );
      }

      os << boost::algorithm::join( properties, "," );
    }

  private:
    const dd& _graph;
  };

  void dd_to_dot( const dd& graph, const std::string& filename )
  {
    std::filebuf fb;
    fb.open( filename.c_str(), std::ios::out );
    std::ostream os( &fb );
    boost::write_graphviz( os, graph, vertex_drawer( graph ), edge_drawer( graph ) );
    fb.close();
  }

  unsigned dd_node_var( const dd_node& node, const dd& graph )
  {
    return get( boost::vertex_name, graph )[node].var;
  }

  bool dd_node_is_constant( const dd_node& node, const dd& graph )
  {
    return !out_degree( node, graph );
  }

  dd_node dd_root_get_function_root( const dd_node& node, const dd& graph )
  {
    boost::graph_traits<dd>::in_edge_iterator itEdge, itEnd;

    for ( boost::tie( itEdge, itEnd ) = in_edges( node, graph ); itEdge != itEnd; ++itEdge )
    {
      if ( in_degree( source( *itEdge, graph ), graph ) == 0 )
      {
        return source( *itEdge, graph );
      }
    }

    assert( false );
    return dd_node();
  }

  bool dd_root_is_complemented( const dd_node& node, const dd& graph )
  {
    return get( boost::edge_name, graph )[*out_edges( dd_root_get_function_root( node, graph ), graph ).first].complemented;
  }

  const std::vector<std::string>& dd_labels( const dd& graph )
  {
    return get_property( graph, boost::graph_name ).labels;
  }

  const std::vector<dd_node>& dd_roots( const dd& graph )
  {
    return get_property( graph, boost::graph_name ).roots;
  }

  ////////////////////////////////////////////////////////////////////////////////
  // KFDD Code                                                                  //
  ////////////////////////////////////////////////////////////////////////////////
  dd_from_kfdd_settings::dd_from_kfdd_settings()
    : default_decomposition( 0 ),
      reordering( 0 ),
      sift_factor( 2.5 ),
      sifting_growth_limit( 'a' ),
      sifting_method( 'v' ),
      node_count( 0 )
  {
  }

  bool OKFDD_IsComplement( utnode* node )
  {
    return ((ulint)node & 1) == 1;
  }

  bool OKFDD_IsConstant( dd_man* manager, utnode* node )
  {
    return ( node == manager->OKFDD_ONE || node == manager->OKFDD_ZERO );
  }

  utnode* OKFDD_Regular( utnode* node )
  {
    return (m_and(node));
  }

  bool OKFDD_Value( dd_man* manager, utnode* node )
  {
    if ( OKFDD_IsConstant( manager, node ) )
    {
      return node == manager->OKFDD_ONE;
    }
    else
    {
      assert( false );
      return false;
    }
  }

  dd_node dd_from_kfdd( dd& graph, dd_man* manager, utnode* node, std::map<utnode*, dd_node>& visited_map )
  {
    if ( visited_map.find( node ) != visited_map.end() )
    {
      return visited_map[node];
    }

    dd_node v = boost::add_vertex( graph );

    if ( !OKFDD_IsConstant( manager, node ) )
    {
      dd_node low  = dd_from_kfdd( graph, manager, OKFDD_Regular( (m_and(node))->lo_p ), visited_map );
      dd_node high = dd_from_kfdd( graph, manager, OKFDD_Regular( (m_and(node))->hi_p ), visited_map );

      add_edge( v, low, graph );
      dd_edge edge;
      bool    ok;
      boost::tie( edge, ok ) = add_edge( v, high, graph );
      get( boost::edge_name, graph )[edge].complemented = OKFDD_IsComplement( (m_and(node))->hi_p );

      get( boost::vertex_name, graph )[v].var = manager->OKFDD_Label( node ) - 1;
      get( boost::vertex_name, graph )[v].dtl = (unsigned short)manager->OKFDD_PI_DTL_Table[manager->OKFDD_Label( node )];
    }
    else
    {
      get( boost::vertex_name, graph )[v].var = OKFDD_Value( manager, node );
    }

    visited_map[node] = v;

    return v;
  }

  void dd_from_kfdd( dd& graph, dd_man* manager, const std::vector<utnode*>& nodes )
  {
    std::map<utnode*, dd_node> visited_map;

    for ( std::vector<utnode*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it )
    {
      dd_node v = boost::add_vertex( graph );
      bool ok;
      dd_edge edge;
      dd_node v2 = dd_from_kfdd( graph, manager, *it, visited_map );
      boost::tie( edge, ok ) = add_edge( v, v2, graph );
      get( boost::edge_name, graph )[edge].complemented = OKFDD_IsComplement( *it );

      get( boost::vertex_name, graph )[v].var      = get_property( graph, boost::graph_name ).labels.size() - nodes.size() + ( it - nodes.begin() );
      get_property( graph, boost::graph_name ).roots += v2;
    }
  }

  void dd_from_kfdd( dd& graph, const std::string& filename, const dd_from_kfdd_settings& settings )
  {
    unsigned char      ut_hashsize  = 0;
    unsigned long      ct_hashsize  = 5003;
    unsigned long int  rc_cachesize = 1000;
    unsigned char      ct_searchlen = 3;
    unsigned short int var_lim     = 20000;

    dd_man* dd_manager = OKFDD_Init( ut_hashsize,
                                     ct_hashsize,
                                     rc_cachesize,
                                     ct_searchlen,
                                     var_lim );

    /* set some construction parameters */
    dd_manager->OKFDD_Outputflags  = 0;
    dd_manager->OKFDD_Version_Wait = false;
    dd_manager->OKFDD_DTL_Default  = settings.default_decomposition;
    dd_manager->OKFDD_Tempfactor   = 3;
    dd_manager->OKFDD_Siftfactor   = 3;
    dd_manager->OKFDD_Temproutine  = 3;
    dd_manager->OKFDD_Interleaving = true;

    std::string input_names, output_names;
    dd_manager->OKFDD_Read_BLIF( const_cast<char*>( filename.c_str() ), 0, &INTERHAND, 0, 0, &input_names, &output_names );

    unsigned npi = dd_manager->OKFDD_P_I;
    unsigned npo = dd_manager->OKFDD_P_O;

    switch ( settings.reordering )
    {
    case 0: //kfdd_synthesis_reordering_none:
      break;

    case 1: //kfdd_synthesis_reordering_exact_dtl_friedman:
      dd_manager->OKFDD_DTL_Friedman( 0, npi - 1 );
      break;

    case 2: //kfdd_synthesis_reordering_exact_dtl_permutation:
      dd_manager->OKFDD_DTL_Permutation( 0, npi - 1 );
      break;

    case 3: //kfdd_synthesis_reordering_dtl_sifting:
      dd_manager->OKFDD_DTL_Sifting( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method );
      break;

    case 4: //kfdd_synthesis_reordering_exact_friedman:
      dd_manager->OKFDD_Friedman( 0, npi - 1 );
      break;

    case 5: //kfdd_synthesis_reordering_exact_permutation:
      dd_manager->OKFDD_Permutation( 0, npi - 1 );
      break;

    case 6: //kfdd_synthesis_reordering_sifting:
      dd_manager->OKFDD_Sifting( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method );
      break;

    case 7: //kfdd_synthesis_reordering_sifting_and_dtl_sifting:
      dd_manager->OKFDD_Sifting( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method );
      dd_manager->OKFDD_DTL_Sifting( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method );
      break;

    case 8: //kfdd_synthesis_reordering_inverse:
      dd_manager->OKFDD_Inversion( 0, npi - 1 );
      break;

    case 9: //kfdd_synthesis_reordering_quantum_sifting line-sifting:
      dd_manager->OKFDD_Sifting( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method );
      dd_manager->OKFDD_DTL_Sifting( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method );
      dd_manager->OKFDD_DTL_Sifting_Quantum( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method, 1 );
      break;

    case 10: //kfdd_synthesis_reordering_quantum_sifting quantumcost-sifting:
      dd_manager->OKFDD_Sifting( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method );
      dd_manager->OKFDD_DTL_Sifting( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method );
      dd_manager->OKFDD_DTL_Sifting_Quantum( 0, npi - 1, settings.sift_factor, settings.sifting_growth_limit, settings.sifting_method, 0 );
      break;
    }

    std::vector<std::string> labels;
    boost::algorithm::trim( input_names );
    boost::algorithm::trim( output_names );
    boost::algorithm::split( labels, input_names, boost::is_any_of( " " ) );
    get_property( graph, boost::graph_name ).ninputs = labels.size();
    std::copy( labels.begin(), labels.end(), std::back_inserter( get_property( graph, boost::graph_name ).labels ) );
    boost::algorithm::split( labels, output_names, boost::is_any_of( " " ) );
    std::copy( labels.begin(), labels.end(), std::back_inserter( get_property( graph, boost::graph_name ).labels ) );

    std::vector<utnode*> nodes;
    for ( unsigned i = 0; i < npo; ++i )
    {
      nodes += dd_manager->OKFDD_PO_Root_Table( dd_manager->OKFDD_PO_Table( i ) );
    }

    dd_from_kfdd( graph, dd_manager, nodes );

    if ( settings.node_count )
    {
      // TODO node counting seems to be wrong in PUMA
      *settings.node_count = dd_manager->OKFDD_Size_all();
      //*settings.node_count = dd_manager->OKFDD_Now_size_i;
      // TODO This fixes node counting
      //*settings.node_count = boost::num_vertices( graph ) - 1u - ( get_property( graph, boost::graph_name ).labels.size() - get_property( graph, boost::graph_name ).ninputs );
    }
  }

////////////////////////////////////////////////////////////////////////////////
// BDD Code                                                                   //
////////////////////////////////////////////////////////////////////////////////

  dd_from_bdd_settings::dd_from_bdd_settings()
    : complemented_edges( true ),
      reordering( CUDD_REORDER_SIFT ),
      node_count( 0 )
  {
  }

  dd_node dd_from_bdd( dd& graph, DdNode* node, std::map<DdNode*, dd_node>& visited_map )
  {
    if ( visited_map.find( node ) != visited_map.end() )
    {
      return visited_map[node];
    }

    dd_node v = boost::add_vertex( graph );

    if ( !cuddIsConstant( node ) )
    {
      dd_node low  = dd_from_bdd( graph, Cudd_Regular( cuddE( node ) ), visited_map );
      dd_node high = dd_from_bdd( graph, cuddT( node ), visited_map );

      dd_edge edge;
      bool    ok;
      boost::tie( edge, ok ) = add_edge( v, low, graph );
      get( boost::edge_name, graph )[edge].complemented = Cudd_IsComplement( cuddE( node ) );

      add_edge( v, high, graph );

      get( boost::vertex_name, graph )[v].var = node->index;
    }
    else
    {
      get( boost::vertex_name, graph )[v].var = cuddV( node );
    }

    visited_map[node] = v;

    return v;
  }

  void dd_from_bdd( dd& graph, const std::vector<DdNode*>& nodes )
  {
    std::map<DdNode*, dd_node> visited_map;

    for ( std::vector<DdNode*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it ) {
      dd_node v = boost::add_vertex( graph );
      bool ok;
      dd_edge edge;
      dd_node v2 = dd_from_bdd( graph, Cudd_Regular( *it ), visited_map );
      boost::tie( edge, ok ) = add_edge( v, v2, graph );
      get( boost::edge_name, graph )[edge].complemented = Cudd_IsComplement( *it );

      get( boost::vertex_name, graph )[v].var = get_property( graph, boost::graph_name ).labels.size() - nodes.size() + ( it - nodes.begin() );
      get_property( graph, boost::graph_name ).roots += v2;
    }
  }

  void dd_from_bdd( dd& graph, const std::string& filename, const dd_from_bdd_settings& settings )
  {
    BDDTable bdd;
    read_pla_to_bdd( bdd, filename );

    // Reorder the BDD
    Cudd_ReduceHeap( bdd.cudd, (Cudd_ReorderingType)settings.reordering, 0 );

    // Node Count
    if ( settings.node_count )
    {
      *settings.node_count = Cudd_ReadNodeCount( bdd.cudd );
    }

    // statistics about the BDD
    if ( settings.infofilename.size() )
    {
      FILE *fp = 0;
      fp = fopen( settings.infofilename.c_str(), "w" );
      Cudd_PrintInfo( bdd.cudd, fp );
      fclose( fp );
    }

    // complemented edges
    if ( !settings.complemented_edges )
    {
      for ( unsigned i = 0; i < bdd.outputs.size(); ++i )
      {
        DdNode *tmp = Cudd_BddToAdd( bdd.cudd, bdd.outputs[i].second );
        Cudd_Ref( tmp );
        Cudd_RecursiveDeref( bdd.cudd, bdd.outputs[i].second );
        bdd.outputs[i].second = tmp;
      }
    }

    using boost::adaptors::map_keys;
    using boost::adaptors::map_values;

    std::vector<DdNode*> nodes;
    boost::push_back( nodes, bdd.outputs | map_values );

    boost::push_back( get_property( graph, boost::graph_name ).labels, bdd.inputs | map_keys );
    boost::push_back( get_property( graph, boost::graph_name ).labels, bdd.outputs | map_keys );

    get_property( graph, boost::graph_name ).ninputs = bdd.inputs.size();

    dd_from_bdd( graph, nodes );
  }

  void dd_from_bdd( dd& graph, bdd_function_t& bdds, const dd_from_bdd_settings& settings )
  {
    bdds.first.ReduceHeap( (Cudd_ReorderingType)settings.reordering, 0 );

    if ( settings.node_count )
    {
      *settings.node_count = bdds.first.ReadNodeCount();
    }

    std::vector<DdNode*> nodes;
    nodes.reserve( bdds.second.size() );
    std::vector<ADD>     adds;

    if ( settings.complemented_edges )
    {
      for ( const auto& f : bdds.second )
      {
        nodes.push_back( f.getNode() );
      }
    }
    else
    {
      for ( const auto& f : bdds.second )
      {
        adds.push_back( f.Add() );
        nodes.push_back( adds.back().getNode() );
      }
    }

    /* I/O */
    auto& info = get_property( graph, boost::graph_name );
    for ( auto i = 0; i < bdds.first.ReadSize(); ++i )
    {
      info.labels.push_back( boost::str( boost::format( "i%d" ) % i ) );
    }

    for ( auto i = 0u; i < bdds.second.size(); ++i )
    {
      info.labels.push_back( boost::str( boost::format( "o%d" ) % i ) );
    }

    info.ninputs = bdds.first.ReadSize();

    dd_from_bdd( graph, nodes );
  }

  ////////////////////////////////////////////////////////////////////////////////
  // DD Synthesis                                                               //
  ////////////////////////////////////////////////////////////////////////////////
  struct data
  {
    unsigned lines;

    std::vector<int> constantValue;
    std::vector<int> lineNeeded;
    std::map<dd_node, unsigned> node2line;

    unsigned up( unsigned cv )
    {
      unsigned ret = lines++;
      lineNeeded += -1;
      constantValue += cv;
      return ret;
    }
  };

  int reversible_generator( circuit& circ, data& d, unsigned index, unsigned dtl, int low, int high, bool low_complemented, bool high_complemented )
  {
    if ( low >= 0 && high >= 0 )
    {
      if ( low == high ) // ll case
      {
        switch ( dtl )
        {
        case 0:
          if ( high_complemented )
          {
            if ( d.lineNeeded[high] )
            {
              unsigned tmpLine = d.up( 0 );

              append_cnot( circ, index, tmpLine );
              append_cnot( circ, low, tmpLine );

              return tmpLine;
            }
            else
            {
              append_cnot( circ, index, low );

              return low;
            }
          }
          else if ( low_complemented )
          {
            if ( d.lineNeeded[high] )
            {
              unsigned tmpLine = d.up( 1 );

              append_cnot( circ, index, tmpLine );
              append_cnot( circ, low, tmpLine );

              return tmpLine;
            }
            else
            {
              append_cnot( circ, index, low );
              append_not( circ, low );

              return low;
            }
          }
          break;

        case 1:
          if ( high_complemented )
          {
            unsigned tmpLine = d.up( 0 );

            append_toffoli( circ )( index, low )( tmpLine );
            append_cnot( circ, index, tmpLine );
            append_cnot( circ, low, tmpLine );

            return tmpLine;
          }
          else if ( low_complemented )
          {
          }
          else
          {
            unsigned tmpLine = d.up( 0 );

            append_toffoli( circ )( index, low )( tmpLine );
            append_cnot( circ, low, tmpLine );

            return tmpLine;
          }
          break;

        case 2:
          if ( high_complemented )
          {
            unsigned tmpLine = d.up( 1 );

            append_toffoli( circ )( index, low )( tmpLine );
            append_cnot( circ, index, tmpLine );

            return tmpLine;
          }
          else if ( low_complemented )
          {
          }
          else
          {
            unsigned tmpLine = d.up( 0 );

            append_toffoli( circ )( index, low )( tmpLine );

            return tmpLine;
          }
          break;
        }
      }
      else if ( d.lineNeeded[low] || d.lineNeeded[high] )
      {
        switch ( dtl )
        {
        case 0:
          if ( high_complemented )
          {
            unsigned tmpLine = d.up( 0 );

            append_cnot( circ, index, tmpLine );
            append_cnot( circ, low, tmpLine );
            append_toffoli( circ )( index, high )( tmpLine );
            append_toffoli( circ )( index, low )( tmpLine );

            return tmpLine;
          }
          else if ( low_complemented )
          {
            unsigned tmpLine = d.up( 1 );

            append_cnot( circ, index, tmpLine );
            append_cnot( circ, low, tmpLine );
            append_toffoli( circ )( index, high )( tmpLine );
            append_toffoli( circ )( index, low )( tmpLine );

            return tmpLine;
          }
          else
          {
            unsigned tmpLine = d.up( 0 );

            append_cnot( circ, low, tmpLine );
            append_toffoli( circ )( index, high )( tmpLine );
            append_toffoli( circ )( index, low )( tmpLine );

            return tmpLine;
          }
          break;

        case 1:
          if ( high_complemented )
          {
            unsigned tmpLine = d.up( 0 );

            append_toffoli( circ )( index, high )( tmpLine );
            append_cnot( circ, low, tmpLine );
            append_cnot( circ, index, tmpLine );

            return tmpLine;
          }
          else if ( low_complemented )
          {
          }
          else
          {
            unsigned tmpLine = d.up( 0 );

            append_toffoli( circ )( index, high )( tmpLine );
            append_cnot( circ, low, tmpLine );

            return tmpLine;
          }
          break;

        case 2:
          if ( high_complemented )
          {
            unsigned tmpLine = d.up( 1 );

            append_toffoli( circ )( index, high )( tmpLine );
            append_cnot( circ, low, tmpLine );
            append_cnot( circ, high, tmpLine );
            append_cnot( circ, index, tmpLine );

            return tmpLine;
          }
          else if ( low_complemented )
          {
          }
          else
          {
            unsigned tmpLine = d.up( 0 );

            append_toffoli( circ )( index, high )( tmpLine );
            append_cnot( circ, low, tmpLine );
            append_cnot( circ, high, tmpLine );

            return tmpLine;
          }
          break;
        }
      }
      else
      {
        switch ( dtl )
        {
        case 0:
          if ( high_complemented )
          {
            append_toffoli( circ )( index, low )( high );
            append_cnot( circ, index, low );
            append_toffoli( circ )( high, index )( low );

            return low;
          }
          else if ( low_complemented )
          {
            append_not( circ, low );
            append_toffoli( circ )( low, index )( high );
            append_toffoli( circ )( high, index )( low );

            return low;
          }
          else
          {
            append_cnot( circ, low, high );
            append_toffoli( circ )( high, index )( low );

            return low;
          }
          break;

        case 1:
          if ( high_complemented )
          {
            append_toffoli( circ )( index, high )( low );
            append_cnot( circ, index, low );

            return low;
          }
          else if ( low_complemented )
          {
          }
          else
          {
            append_toffoli( circ )( index, high )( low );

            return low;
          }
          break;

        case 2:
          if ( high_complemented )
          {
            append_not( circ, high );
            append_toffoli( circ )( index, high )( low );
            append_cnot( circ, low, high );

            return high;
          }
          else if ( low_complemented )
          {
          }
          else
          {
            append_toffoli( circ )( index, high )( low );
            append_cnot( circ, low, high );

            return high;
          }
          break;
        }
      }
    }
    else if ( low == -1 && high >= 0 )
    {
      switch ( dtl )
      {
      case 0:
        if ( high_complemented )
        {
          unsigned tmpLine = d.up( 1 );

          append_toffoli( circ )( index, high )( tmpLine );

          return tmpLine;
        }
        else if ( low_complemented )
        {
        }
        else
        {
          unsigned tmpLine = d.up( 1 );

          append_toffoli( circ )( index, high )( tmpLine );
          append_cnot( circ, index, tmpLine );

          return tmpLine;
        }
        break;

      case 1:
        if ( high_complemented )
        {
          unsigned tmpLine = d.up( 1 );

          append_toffoli( circ )( index, high )( tmpLine );
          append_cnot( circ, index, tmpLine );

          return tmpLine;
        }
        else if ( low_complemented )
        {
        }
        else
        {
          unsigned tmpLine = d.up( 1 );

          append_toffoli( circ )( index, high )( tmpLine );

          return tmpLine;
        }
        break;

      case 2:
        if ( high_complemented )
        {
          unsigned tmpLine = d.up( 0 );

          append_toffoli( circ )( index, high )( tmpLine );
          append_cnot( circ, high, tmpLine );
          append_cnot( circ, index, tmpLine );

          return tmpLine;
        }
        else if ( low_complemented )
        {
        }
        else
        {
          unsigned tmpLine = d.up( 1 );

          append_toffoli( circ )( index, high )( tmpLine );
          append_cnot( circ, high, tmpLine );

          return tmpLine;
        }
      }
    }
    else if ( low == -2 && high >= 0 )
    {
      switch ( dtl )
      {
      case 0:
        if ( high_complemented )
        {
        }
        else if ( low_complemented )
        {
        }
        else
        {
          unsigned tmpLine = d.up( 0 );

          append_toffoli( circ )( index, high )( tmpLine );

          return tmpLine;
        }
        break;
      }
    }
    else if ( low >= 0 && high == -1 )
    {
      switch ( dtl )
      {
      case 0:
        if ( high_complemented )
        {
        }
        else if ( low_complemented )
        {
          unsigned tmpLine = d.up( 1 );

          append_toffoli( circ )( low, index )( tmpLine );
          append_cnot( circ, low, tmpLine );

          return tmpLine;
        }
        else
        {
          unsigned tmpLine = d.up( 0 );

          append_cnot( circ, index, tmpLine );
          append_toffoli( circ )( low, index )( tmpLine );
          append_cnot( circ, low, tmpLine );

          return tmpLine;
        }
        break;

      case 1:
        if ( high_complemented )
        {
        }
        else if ( low_complemented )
        {
        }
        else
        {
          unsigned tmpLine = d.up( 0 );

          append_cnot( circ, low, tmpLine );
          append_cnot( circ, index, tmpLine );

          return tmpLine;
        }
        break;

      case 2:
        if ( high_complemented )
        {
        }
        if ( low_complemented )
        {
        }
        else
        {
          unsigned tmpLine = d.up( 1 );

          append_cnot( circ, low, tmpLine );
          append_cnot( circ, index, tmpLine );

          return tmpLine;
        }
        break;
      }
    }
    else if ( low >= 0 && high == -2 )
    {
      switch ( dtl )
      {
      case 0:
        if ( high_complemented )
        {
        }
        else if ( low_complemented )
        {
          unsigned tmpLine = d.up( 1 );

          append_cnot( circ, index, tmpLine );
          append_toffoli( circ )( low, index )( tmpLine );
          append_cnot( circ, low, tmpLine );

          return tmpLine;
        }
        else
        {
          unsigned tmpLine = d.up( 0 );

          append_toffoli( circ )( low, index )( tmpLine );
          append_cnot( circ, low, tmpLine );

          return tmpLine;
        }
        break;
      }
    }
    else if ( low == -1 && high == -1 )
    {
      assert( dtl != 0 );
      assert( !low_complemented && !high_complemented );

      switch ( dtl )
      {
      case 1:
        {
          unsigned tmpLine = d.up( 1 );

          append_cnot( circ, index, tmpLine );

          return tmpLine;
        }
        break;

      case 2:
        return index;
        break;
      }
    }
    else if ( low == -1 && high == -2 )
    {
      assert( dtl == 0 );
      unsigned tmpLine = d.up( 1 );

      append_cnot( circ, index, tmpLine );

      return tmpLine;
    }
    else if ( low == -2 && high == -1 )
    {
      return index;
    }

    std::cerr << "Missing: index: " << index << " dtl: " << dtl << " low: " << low << " high: " << high << " low_complemented: " << low_complemented << " high_complemented: " << high_complemented  << std::endl;

    assert( false );

    return -1;
  }

  int node2line( const dd_node& node, const dd& graph, bool is_complemented, data& d )
  {
    int line = -1;

    if ( dd_node_is_constant( node, graph ) )
    {
      if ( is_complemented )
      {
        line = dd_node_var( node, graph ) == 1 ? -2 : -1;
      }
      else
      {
        line = dd_node_var( node, graph ) == 1 ? -1 : -2;
      }
    }
    else
    {
      std::map<dd_node, unsigned>::const_iterator it = d.node2line.find( node );
      assert( it != d.node2line.end() );
      assert( d.lineNeeded[it->second] > 0 || it->second < get_property( graph, boost::graph_name ).ninputs );
      line = it->second;
      if ( line >= (int)get_property( graph, boost::graph_name ).ninputs )
      {
        --d.lineNeeded[line];
      }
    }

    return line;
  }

  void dd_synthesis( circuit& circ, data& d, const dd_node& node, const dd& graph, boost::function<int(circuit&, data&, unsigned, unsigned short, int, int, bool, bool)> gate_inserter )
  {

    if ( dd_node_is_constant( node, graph ) )
    {
      // should only happen, if PO is constant
      unsigned tmpLine = d.lines;
      ++d.lines;
      d.lineNeeded += -1;
      d.constantValue += (int)dd_node_var( node, graph );
      d.node2line.insert( std::make_pair( node, tmpLine ) );
      return;
    }

    if ( d.node2line.find( node ) != d.node2line.end() )
    {
      // already visited
      return;
    }

    // get low and high
    bool low_complemented, high_complemented;
    dd_node lowNode, highNode;

    boost::graph_traits<dd>::out_edge_iterator itEdge = out_edges( node, graph ).first;
    low_complemented = get( boost::edge_name, graph )[*itEdge].complemented;
    lowNode = target( *itEdge, graph );
    ++itEdge;
    high_complemented = get( boost::edge_name, graph )[*itEdge].complemented;
    highNode = target( *itEdge, graph );

    if ( !dd_node_is_constant( highNode, graph ) )
    {
      dd_synthesis( circ, d, highNode, graph, gate_inserter );
    }
    if ( !dd_node_is_constant( lowNode, graph ) )
    {
      dd_synthesis( circ, d, lowNode, graph, gate_inserter );
    }

    unsigned index = dd_node_var( node, graph );
    int high       = node2line( highNode, graph, high_complemented, d );
    int low        = node2line( lowNode, graph, low_complemented, d );

    if ( high < 0 )
    {
      high_complemented = false;
    }
    if ( low < 0 )
    {
      low_complemented = false;
    }

    unsigned short dtl = get( boost::vertex_name, graph )[node].dtl;
    int out            = gate_inserter( circ, d, index, dtl, low, high, low_complemented, high_complemented );

    assert( out != -1 );
    d.node2line.insert( std::make_pair( node, out ) );
    assert( (int)d.lineNeeded.size() > out );

    d.lineNeeded[out] = in_degree( node, graph );

  }

  void dd_synthesis( circuit& circ, const dd& graph, boost::function<int(circuit&, data&, unsigned, unsigned short, int, int, bool, bool)> gate_inserter )
  {
    // empty circuit
    clear_circuit( circ );

    // get number of inputs
    unsigned ninputs = get_property( graph, boost::graph_name ).ninputs;

    data d;
    d.lines = ninputs;
    d.constantValue.resize( d.lines, -1 );
    d.lineNeeded.resize( d.lines, -1 );

    // recursion
    for ( const unsigned& node_index : dd_roots( graph ) )
    {
      dd_synthesis( circ, d, node_index, graph, gate_inserter );
    }

    for ( const unsigned& node_index : dd_roots( graph ) )
    {
      if ( dd_root_is_complemented( node_index, graph ) )
      {
        append_not( circ, d.node2line[node_index] );
      }
    }

    circ.set_lines( d.lines );

    // set inputs and constants
    std::vector<std::string> inputs( d.lines );
    std::vector<constant> constants( d.lines, constant() );
    std::copy( dd_labels( graph ).begin(), dd_labels( graph ).begin() + ninputs, inputs.begin() );
    std::transform( d.constantValue.begin() + ninputs, d.constantValue.end(), inputs.begin() + ninputs, []( int n ) { return boost::lexical_cast<std::string>( n ); } );
    std::transform( d.constantValue.begin() + ninputs, d.constantValue.end(), constants.begin() + ninputs, []( int n ) { return boost::lexical_cast<bool>( n ); } );
    circ.set_inputs( inputs );
    circ.set_constants( constants );

    // set outputs and garbage
    std::vector<std::string> outputs( d.lines, "g" );
    std::vector<bool> garbage( d.lines, true );
    for ( const unsigned& node_index : dd_roots( graph ) )
    {
      unsigned index = d.node2line[node_index];
      outputs[index] = dd_labels( graph ).at( get( boost::vertex_name, graph )[dd_root_get_function_root( node_index, graph )].var );
      garbage[index] = false;
    }
    circ.set_outputs( outputs );
    circ.set_garbage( garbage );

  }

  void dd_synthesis( circuit& circ, const dd& graph )
  {
    dd_synthesis( circ, graph, &reversible_generator );
  }

}

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End:


//---------------------------------------------------------------------------//
//!
//! \file   MonteCarlo_ParticleSimulationManager_def.hpp
//! \author Alex Robinson
//! \brief  The particle simulation manager class definition.
//!
//---------------------------------------------------------------------------//

#ifndef FACEMC_PARTICLE_SIMULATION_MANAGER_DEF_HPP
#define FACEMC_PARTICLE_SIMULATION_MANAGER_DEF_HPP

// Boost Includes
#include <boost/bind.hpp>

// FRENSIE Includes
#include "MonteCarlo_ParticleBank.hpp"
#include "MonteCarlo_SourceModuleInterface.hpp"
#include "MonteCarlo_EstimatorModuleInterface.hpp"
#include "MonteCarlo_CollisionModuleInterface.hpp"
#include "MonteCarlo_SimulationGeneralProperties.hpp"
#include "MonteCarlo_SimulationNeutronProperties.hpp"
#include "MonteCarlo_SimulationElectronProperties.hpp"
#include "MonteCarlo_SimulationPhotonProperties.hpp"
#include "Geometry_ModuleInterface.hpp"
#include "Utility_RandomNumberGenerator.hpp"
#include "Utility_ContractException.hpp"
#include "Utility_GlobalOpenMPSession.hpp"
#include "MonteCarlo_ElectronState.hpp"
#include "MonteCarlo_PhotonState.hpp"
#include "MonteCarlo_NeutronState.hpp"

namespace MonteCarlo{

// Constructor for multiple threads
/*! \details If OpenMP is not being used, the number of threads requested will
 * be ignored.
 */
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
ParticleSimulationManager<GeometryHandler,
			  SourceHandler,
			  EstimatorHandler,
			  CollisionHandler>::ParticleSimulationManager(
		       const unsigned long long number_of_histories,
		       const unsigned long long start_history,
		       const unsigned long long previously_completed_histories,
		       const double previous_run_time )
  : d_start_history( start_history ),
    d_history_number_wall( start_history + number_of_histories ),
    d_histories_completed( previously_completed_histories ),
    d_end_simulation( false ),
    d_previous_run_time( previous_run_time ),
    d_start_time( 0.0 ),
    d_end_time( 0.0 )
{
  // At least one history must be simulated
  testPrecondition( number_of_histories > 0 );

  // Assign the functions based on the mode
  ParticleModeType mode = SimulationGeneralProperties::getParticleMode();

  switch( mode )
  {
  case NEUTRON_MODE:
  {
    d_simulate_neutron = boost::bind<void>( &ParticleSimulationManager<GeometryHandler,SourceHandler,EstimatorHandler,CollisionHandler>::simulateParticle<NeutronState>,
					    boost::cref( *this ),
					    _1,
					    _2 );
    d_simulate_photon = boost::bind<void>( &ParticleSimulationManager<GeometryHandler,SourceHandler,EstimatorHandler,CollisionHandler>::ignoreParticle<PhotonState>,
					    boost::cref( *this ),
					    _1,
					    _2 );
    d_simulate_electron = boost::bind<void>( &ParticleSimulationManager<GeometryHandler,SourceHandler,EstimatorHandler,CollisionHandler>::ignoreParticle<ElectronState>,
					    boost::cref( *this ),
					    _1,
					    _2 );
    break;
  }
  case PHOTON_MODE:
  {
    d_simulate_photon = boost::bind<void>( &ParticleSimulationManager<GeometryHandler,SourceHandler,EstimatorHandler,CollisionHandler>::simulateParticle<PhotonState>,
					    boost::cref( *this ),
					    _1,
					    _2 );
    d_simulate_neutron = boost::bind<void>( &ParticleSimulationManager<GeometryHandler,SourceHandler,EstimatorHandler,CollisionHandler>::ignoreParticle<NeutronState>,
					    boost::cref( *this ),
					    _1,
					    _2 );
    d_simulate_electron = boost::bind<void>( &ParticleSimulationManager<GeometryHandler,SourceHandler,EstimatorHandler,CollisionHandler>::ignoreParticle<ElectronState>,
					    boost::cref( *this ),
					    _1,
					    _2 );
    break;
  }
  case ELECTRON_MODE:
  {
    d_simulate_electron = boost::bind<void>( &ParticleSimulationManager<GeometryHandler,SourceHandler,EstimatorHandler,CollisionHandler>::simulateParticle<ElectronState>,
					     boost::cref( *this ),
					     _1,
					     _2 );
    d_simulate_neutron = boost::bind<void>( &ParticleSimulationManager<GeometryHandler,SourceHandler,EstimatorHandler,CollisionHandler>::ignoreParticle<NeutronState>,
					    boost::cref( *this ),
					    _1,
					    _2 );
    d_simulate_photon = boost::bind<void>( &ParticleSimulationManager<GeometryHandler,SourceHandler,EstimatorHandler,CollisionHandler>::ignoreParticle<PhotonState>,
					   boost::cref( *this ),
					   _1,
					   _2 );
    break;
  }
  default:
    THROW_EXCEPTION( std::runtime_error,
		     "Error: particle mode " << mode << " is not currently "
		     << "supported by the particle simulation manager." );
  }
}

// Run the simulation set up by the user
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::runSimulation()
{
  std::cout << "Starting simulation ... ";
  std::cout.flush();

  // Set up the random number generator for the number of threads requested
  Utility::RandomNumberGenerator::createStreams();

  // Enable geometry thread support
  GMI::enableThreadSupport(
	         Utility::GlobalOpenMPSession::getRequestedNumberOfThreads() );

  // Enable estimator thread support
  EMI::enableThreadSupport(
		 Utility::GlobalOpenMPSession::getRequestedNumberOfThreads() );

  // Set the start time
  this->setStartTime( Utility::GlobalOpenMPSession::getTime() );

  // Simulate the batch
  this->runSimulationBatch( d_start_history, d_history_number_wall );

  // Set the end time
  this->setEndTime( Utility::GlobalOpenMPSession::getTime() );

  std::cout << "done." << std::endl;
}

// Run the simulation batch
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::runSimulationBatch(
                            const unsigned long long batch_start_history,
			    const unsigned long long batch_end_history )
{
  // Make sure the history range is valid
  testPrecondition( batch_start_history <= batch_end_history );
  testPrecondition( batch_start_history >= d_start_history );
  testPrecondition( batch_end_history <= d_history_number_wall );

  #pragma omp parallel num_threads( Utility::GlobalOpenMPSession::getRequestedNumberOfThreads() )
  {
    // Create a bank for each thread
    ParticleBank bank;

    #pragma omp for
    for( unsigned long long history = batch_start_history; history < batch_end_history; ++history )
    {
      // Do useful work unless the user requests an end to the simulation
      #pragma omp flush( d_end_simulation )
      if( !d_end_simulation )
      {
	// Initialize the random number generator for this history
	Utility::RandomNumberGenerator::initialize( history );

	// Sample a particle state from the source
	SMI::sampleParticleState( bank, history );

	// Determine the starting cell of the particle
	for( unsigned i = 0; i < bank.size(); ++i )
	{
	  typename GMI::InternalCellHandle start_cell;

	  try{
	    start_cell = GMI::findCellContainingPoint( bank.top().ray() );
	  }
	  CATCH_LOST_SOURCE_PARTICLE_AND_CONTINUE( bank );

	  bank.top().setCell( start_cell );

	  EMI::updateEstimatorsFromParticleGenerationEvent( bank.top() );
	}

	// This history only ends when the particle bank is empty
	while( bank.size() > 0 )
	{
	  switch( bank.top().getParticleType() )
	  {
	  case NEUTRON:
	    d_simulate_neutron( dynamic_cast<NeutronState&>( bank.top() ),
				bank );
	    break;
	  case PHOTON:
	    d_simulate_photon( dynamic_cast<PhotonState&>( bank.top() ),
			       bank );
	    break;
	  case ELECTRON:
	    d_simulate_electron( dynamic_cast<ElectronState&>( bank.top() ),
	    			 bank );
	    break;
	  default:
	    THROW_EXCEPTION( std::logic_error,
			     "Error: particle type "
			     << bank.top().getParticleType() <<
			     " is not currently supported!" );
	  }

	bank.pop();
	}

	// Commit all estimator history contributions
	EMI::commitEstimatorHistoryContributions();

	// Increment the number of histories completed
        #pragma omp atomic
	++d_histories_completed;
      }
    }
  }
}

// Set the number of particle histories to simulate
template<typename GeometryHandler,
         typename SourceHandler,
         typename EstimatorHandler,
         typename CollisionHandler>
template<typename ParticleStateType>
void ParticleSimulationManager<GeometryHandler,
                               SourceHandler,
                               EstimatorHandler,
                               CollisionHandler>::simulateParticle(
                                                   ParticleStateType& particle,
						   ParticleBank& bank ) const
{
  // Particle tracking information
  double distance_to_surface_hit, op_to_surface_hit, remaining_subtrack_op;
  double subtrack_start_time;
  double ray_start_point[3];

  // Cache the start point of the ray
  ray_start_point[0] = particle.getXPosition();
  ray_start_point[1] = particle.getYPosition();
  ray_start_point[2] = particle.getZPosition();

  // Surface information
  typename GMI::InternalSurfaceHandle surface_hit;
  Teuchos::Array<double> surface_normal( 3 );

  // Cell information
  typename GMI::InternalCellHandle cell_entering, cell_leaving;
  double cell_total_macro_cross_section;

  // Check if the particle energy is below the cutoff
  if( particle.getEnergy() < SimulationGeneralProperties::getMinParticleEnergy<ParticleStateType>() )
    particle.setAsGone();

  while( !particle.isLost() && !particle.isGone() )
  {
    // Sample the mfp traveled by the particle on this subtrack
    remaining_subtrack_op = CMI::sampleOpticalPathLength();

    // Ray trace until the necessary number of optical paths have be traveled
    while( true )
    {
      // Fire a ray at the cell currently containing the particle
      try{
  	distance_to_surface_hit = 0.0;

  	GMI::fireRay( particle.ray(),
  		      particle.getCell(),
  		      surface_hit,
  		      distance_to_surface_hit );
      }
      CATCH_LOST_PARTICLE_AND_BREAK( particle );

      // Get the total cross section for the cell
      if( !CMI::isCellVoid( particle.getCell(), particle.getParticleType() ) )
      {
      	cell_total_macro_cross_section =
      	  CMI::getMacroscopicTotalCrossSection( particle );
      }
      else
  	cell_total_macro_cross_section = 0.0;

      // Convert the distance to the surface to optical path
      op_to_surface_hit =
  	distance_to_surface_hit*cell_total_macro_cross_section;

      // Get the start time of this subtrack
      subtrack_start_time = particle.getTime();

      if( op_to_surface_hit < remaining_subtrack_op )
      {
  	// Advance the particle to the cell boundary
  	particle.advance( distance_to_surface_hit );

  	// Get the surface normal at the intersection point
  	GMI::getSurfaceNormal( surface_hit,
  			       particle.getPosition(),
  			       surface_normal.getRawPtr() );

  	cell_leaving = particle.getCell();

  	// Find the cell on the other side of the surface hit
  	try{
  	  cell_entering = GMI::findCellContainingPoint( particle.ray(),
  							cell_leaving,
  							surface_hit );
  	}
  	CATCH_LOST_PARTICLE_AND_BREAK( particle );

  	particle.setCell( cell_entering );

  	// Update estimators
  	EMI::updateEstimatorsFromParticleCrossingSurfaceEvent(
  						  particle,
  						  cell_entering,
  						  cell_leaving,
  						  surface_hit,
  						  distance_to_surface_hit,
  						  subtrack_start_time,
  						  surface_normal.getRawPtr() );

  	// Check if a termination cell was encountered
  	if( GMI::isTerminationCell( particle.getCell() ) )
  	{
  	  particle.setAsGone();

  	  break;
  	}

  	// Update the remaining subtrack mfp
  	remaining_subtrack_op -= op_to_surface_hit;
      }

      // A collision occurs in this cell
      else
      {
  	// Advance the particle to the collision site
  	double distance = remaining_subtrack_op/cell_total_macro_cross_section;

  	particle.advance( distance );

  	// Update estimators
  	EMI::updateEstimatorsFromParticleCollidingInCellEvent(
  					  particle,
  					  distance,
  					  subtrack_start_time,
  					  1.0/cell_total_macro_cross_section );

  	EMI::updateEstimatorsFromParticleCollidingGlobalEvent(
  						      particle,
  						      ray_start_point,
  						      particle.getPosition() );

  	// Undergo a collision with the material in the cell
  	CMI::collideWithCellMaterial( particle, bank, true );

  	// Indicate that a collision has occurred
  	GMI::newRay();

  	// Cache the current position of the new ray
  	ray_start_point[0] = particle.getXPosition();
  	ray_start_point[1] = particle.getYPosition();
  	ray_start_point[2] = particle.getZPosition();

  	// Make sure the energy is above the cutoff
  	if( particle.getEnergy() < SimulationGeneralProperties::getMinParticleEnergy<ParticleStateType>() )
  	  particle.setAsGone();

  	// This subtrack is finished
  	break;
      }
    }
  }

  // Update the global estimators
  EMI::updateEstimatorsFromParticleCollidingGlobalEvent(
  						      particle,
  						      ray_start_point,
  						      particle.getPosition() );

  // Indicate that this particle history is complete
  GMI::newRay();
}

// Return the number of histories
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
unsigned long long  ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::getNumberOfHistories() const
{
  return d_history_number_wall - d_start_history;
}

// Return the number of histories completed
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
unsigned long long  ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::getNumberOfHistoriesCompleted() const
{
  return d_histories_completed;
}

// Increment the number of histories completed
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::incrementHistoriesCompleted(
					   const unsigned long long histories )
{
  d_histories_completed += histories;
}

// Set the number of histories completed
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::setHistoriesCompleted(
					   const unsigned long long histories )
{
  d_histories_completed = histories;
}

// Set the start time
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::setStartTime( const double start_time )
{
  d_start_time = start_time;
}

// Set the end time
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::setEndTime( const double end_time )
{
  // Make sure the end time is valid
  testPrecondition( end_time >= d_start_time );

  d_end_time = end_time;
}

// Set the number of particle histories to simulate
template<typename GeometryHandler,
         typename SourceHandler,
         typename EstimatorHandler,
         typename CollisionHandler>
template<typename ParticleStateType>
void ParticleSimulationManager<GeometryHandler,
                               SourceHandler,
                               EstimatorHandler,
                               CollisionHandler>::ignoreParticle(
                                                   ParticleStateType& particle,
						   ParticleBank& bank ) const
{
  particle.setAsGone();
}

// Print the data in all estimators to the desired stream
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::printSimulationSummary(
						       std::ostream &os ) const
{
  os << "!!!Particle Simulation Finished!!!" << std::endl;
  os << "Number of histories completed: " << d_histories_completed <<std::endl;
  os << "Simulation Time (s): " << d_end_time - d_start_time << std::endl;
  os << "Previous Simulation Time (s): " << d_previous_run_time << std::endl;
  os << std::endl;

  EMI::printEstimators( os,
			d_histories_completed,
			d_start_time,
			d_end_time+d_previous_run_time );
}

// Print the data in all estimators to a parameter list
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::exportSimulationData(
				      const std::string& data_file_name ) const
{
  std::cout << "Exporting simulation data ... ";
  std::cout.flush();

  EMI::exportEstimatorData( data_file_name,
  			    d_start_history+d_histories_completed,
  			    d_histories_completed,
  			    d_start_time,
  			    d_end_time+d_previous_run_time,
			    true );

  std::cout << "done." << std::endl;
}

// Signal handler
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::signalHandler(int signal)
{
  // Ask the user what to do
  std::cerr << " Status (s), End (e), Kill (k)" << std::endl;
  std::string option;
  std::cin >> option;

  if( option.compare( "s" ) == 0 )
  {
    printSimulationStateInfo();
  }
  else if( option.compare( "e" ) == 0 )
  {
    d_end_simulation = true;
  }
  else if( option.compare( "k" ) == 0 )
  {
    exit(0);
  }
}

// Print simulation state info in collision handler
template<typename GeometryHandler,
	 typename SourceHandler,
	 typename EstimatorHandler,
	 typename CollisionHandler>
void ParticleSimulationManager<GeometryHandler,
			       SourceHandler,
			       EstimatorHandler,
			       CollisionHandler>::printSimulationStateInfo()
{
  double time = Utility::GlobalOpenMPSession::getTime();

  #pragma omp critical( ostream_update )
  {
    #pragma omp flush( d_histories_completed )
    std::cerr << " History: " << d_histories_completed
	      << " Run Time: " << time - d_start_time
	      << std::endl;
  }
}

} // end MonteCarlo namespace

#endif // end FACEMC_PARTICLE_SIMULATION_MANAGER_DEF_HPP

//---------------------------------------------------------------------------//
// end MonteCarlo_ParticleSimulationManager_def.hpp
//---------------------------------------------------------------------------//


// 2019 Team AobaZero
// This is a work derived from Leela Zero (May 1, 2019).
/*
    This file is part of Leela Zero.
    Copyright (C) 2017-2019 Gian-Carlo Pascutto and contributors

    Leela Zero is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Leela Zero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Leela Zero.  If not, see <http://www.gnu.org/licenses/>.

    Additional permission under GNU GPL version 3 section 7

    If you modify this Program, or any covered work, by linking or
    combining it with NVIDIA Corporation's libraries from the
    NVIDIA CUDA Toolkit and/or the NVIDIA CUDA Deep Neural
    Network library and/or the NVIDIA TensorRT inference library
    (or a modified version of those libraries), containing parts covered
    by the terms of the respective license agreement, the licensors of
    this Program grant you additional permission to convey the resulting
    work.
*/

#include "config.h"

#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <iterator>
#include <memory>
#include <sstream>
#include <string>
#include <boost/utility.hpp>
#include <boost/format.hpp>
#include <boost/spirit/home/x3.hpp>
#ifndef USE_BLAS
#include <Eigen/Dense>
#endif

#ifdef __APPLE__
#include <Accelerate/Accelerate.h>
#endif
#ifdef USE_MKL
#include <mkl.h>
#endif
#ifdef USE_OPENBLAS
#include <cblas.h>
#endif
#include "zlib.h"

#include "Network.h"
#include "CPUPipe.h"
#ifdef USE_OPENCL
#include "OpenCLScheduler.h"
#include "UCTNode.h"
#endif
#include "FastBoard.h"
#include "FastState.h"
#include "FullBoard.h"
#include "GameState.h"
#include "GTP.h"
#include "NNCache.h"
#include "Random.h"
#include "ThreadPool.h"
#include "Timing.h"
#include "Utils.h"

namespace x3 = boost::spirit::x3;
using namespace Utils;

#ifndef USE_BLAS
// Eigen helpers
template <typename T>
using EigenVectorMap =
    Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, 1>>;
template <typename T>
using ConstEigenVectorMap =
    Eigen::Map<const Eigen::Matrix<T, Eigen::Dynamic, 1>>;
template <typename T>
using ConstEigenMatrixMap =
    Eigen::Map<const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>>;
#endif
/*
// Symmetry helper
static std::array<std::array<int, NUM_INTERSECTIONS>,
                  Network::NUM_SYMMETRIES> symmetry_nn_idx_table;
*/
float Network::benchmark_time(int /*centiseconds*/) {
#if 0
    const auto cpus = cfg_num_threads;
    ThreadGroup tg(thread_pool);
    std::atomic<int> runcount{0};

    GameState state;
    state.init_game(BOARD_SIZE, KOMI);

    // As a sanity run, try one run with self check.
    // Isn't enough to guarantee correctness but better than nothing,
    // plus for large nets self-check takes a while (1~3 eval per second)
    get_output(&state, Ensemble::RANDOM_SYMMETRY, -1, false, true, true);

    const Time start;
    for (auto i = size_t{0}; i < cpus; i++) {
        tg.add_task([this, &runcount, start, centiseconds, state]() {
            while (true) {
                runcount++;
                get_output(&state, Ensemble::RANDOM_SYMMETRY, -1, false);
                const Time end;
                const auto elapsed = Time::timediff_centis(start, end);
                if (elapsed >= centiseconds) {
                    break;
                }
            }
        });
    }
    tg.wait_all();

    const Time end;
    const auto elapsed = Time::timediff_centis(start, end);
    return 100.0f * runcount.load() / elapsed;
#endif
	return 0;
}

void Network::benchmark(const GameState* const /*state*/, const int /*iterations*/) {
#if 0
    const auto cpus = cfg_num_threads;
    const Time start;

    ThreadGroup tg(thread_pool);
    std::atomic<int> runcount{0};

    for (auto i = size_t{0}; i < cpus; i++) {
        tg.add_task([this, &runcount, iterations, state]() {
            while (runcount < iterations) {
                runcount++;
                get_output(state, Ensemble::RANDOM_SYMMETRY, -1, false);
            }
        });
    }
    tg.wait_all();

    const Time end;
    const auto elapsed = Time::timediff_seconds(start, end);
    myprintf("%5d evaluations in %5.2f seconds -> %d n/s\n",
             runcount.load(), elapsed, int(runcount.load() / elapsed));
#endif
}

template<class container>
void process_bn_var(container& weights) {
    constexpr float epsilon = 1e-5f;
    for (auto&& w : weights) {
        w = 1.0f / std::sqrt(w + epsilon);
    }
}
template<class container>
void modify_bn_scale_factor(container& weights) {
	const float scale_factor = 1.0f / 999.982f;
    for(auto&& w : weights) {
        w = w * scale_factor;
    }
}

std::vector<float> Network::winograd_transform_f(const std::vector<float>& f,
                                                 const int outputs,
                                                 const int channels) {
    // F(4x4, 3x3) Winograd filter transformation
    // transpose(G.dot(f).dot(G.transpose()))
    // U matrix is transposed for better memory layout in SGEMM
    auto U = std::vector<float>(WINOGRAD_TILE * outputs * channels);
    const auto G = std::array<float, 3 * WINOGRAD_ALPHA>
                    { 1.0f,        0.0f,      0.0f,
                      -2.0f/3.0f, -SQ2/3.0f, -1.0f/3.0f,
                      -2.0f/3.0f,  SQ2/3.0f, -1.0f/3.0f,
                      1.0f/6.0f,   SQ2/6.0f,  1.0f/3.0f,
                      1.0f/6.0f,  -SQ2/6.0f,  1.0f/3.0f,
                      0.0f,        0.0f,      1.0f};

    auto temp = std::array<float, 3 * WINOGRAD_ALPHA>{};

    constexpr auto max_buffersize = 8;
    auto buffersize = max_buffersize;

    if (outputs % buffersize != 0) {
        buffersize = 1;
    }

    std::array<float, max_buffersize * WINOGRAD_ALPHA * WINOGRAD_ALPHA> buffer;

    for (auto c = 0; c < channels; c++) {
        for (auto o_b = 0; o_b < outputs/buffersize; o_b++) {
            for (auto bufferline = 0; bufferline < buffersize; bufferline++) {
                const auto o = o_b * buffersize + bufferline;

                for (auto i = 0; i < WINOGRAD_ALPHA; i++) {
                    for (auto j = 0; j < 3; j++) {
                        auto acc = 0.0f;
                        for (auto k = 0; k < 3; k++) {
                            acc += G[i*3 + k] * f[o*channels*9 + c*9 + k*3 + j];
                        }
                        temp[i*3 + j] = acc;
                    }
                }

                for (auto xi = 0; xi < WINOGRAD_ALPHA; xi++) {
                    for (auto nu = 0; nu < WINOGRAD_ALPHA; nu++) {
                        auto acc = 0.0f;
                        for (auto k = 0; k < 3; k++) {
                            acc += temp[xi*3 + k] * G[nu*3 + k];
                        }
                        buffer[(xi * WINOGRAD_ALPHA + nu) * buffersize + bufferline] = acc;
                    }
                }
            }
            for (auto i = 0; i < WINOGRAD_ALPHA * WINOGRAD_ALPHA; i++) {
                for (auto entry = 0; entry < buffersize; entry++) {
                    const auto o = o_b * buffersize + entry;
                    U[i * outputs * channels
                      + c * outputs
                      + o] =
                    buffer[buffersize * i + entry];
                }
            }
        }
    }

    return U;
}

std::pair<int, int> Network::load_v1_network(std::istream& wtfile) {
    // Count size of the network
    myprintf("Detecting residual layers...");

    // We are version 1 or 2
    if (m_value_head_not_stm) {
        myprintf("v%d...", 2);
    } else {
        myprintf("v%d...", 1);
    }

    // First line was the version number
    auto linecount = size_t{1};
    auto channels = 0;
    auto line = std::string{};
    while (std::getline(wtfile, line)) {
        auto iss = std::stringstream{line};
        // Third line of parameters are the convolution layer biases,
        // so this tells us the amount of channels in the residual layers.
        // We are assuming all layers have the same amount of filters.
        if (linecount == 2) {
            auto count = std::distance(std::istream_iterator<std::string>(iss),
                                       std::istream_iterator<std::string>());
            myprintf("%d channels...", count);
            channels = count;
        }
        linecount++;
    }
    // 1 format id, 1 input layer (4 x weights), 14 ending weights,
    // the rest are residuals, every residual has 8 x weight lines
    auto residual_blocks = linecount - (1 + 4 + 14);
    if (residual_blocks % 8 != 0) {
        myprintf("\nInconsistent number of weights in the file.\n");
        return {0, 0};
    }
	myprintf("linecount=%d...",linecount);
    residual_blocks /= 8;
    myprintf("%d blocks.\n", residual_blocks);

    // Re-read file and process
    wtfile.clear();
    wtfile.seekg(0, std::ios::beg);

    // Get the file format id out of the way
    std::getline(wtfile, line);

    const auto plain_conv_layers = 1 + (residual_blocks * 2);
    const auto plain_conv_wts = plain_conv_layers * 4;
    linecount = 0;
    int num = 0;
    while (std::getline(wtfile, line)) {
        std::vector<float> weights;
        auto it_line = line.cbegin();
        const auto ok = phrase_parse(it_line, line.cend(),
                                     *x3::float_, x3::space, weights);
        num += weights.size();
        if (!ok || it_line != line.cend()) {
            myprintf("\nFailed to parse weight file. Error on line %d.\n",
                    linecount + 2); //+1 from version line, +1 from 0-indexing
            return {0, 0};
        }
        if (linecount < plain_conv_wts) {
            if (linecount % 4 == 0) {
                m_fwd_weights->m_conv_weights.emplace_back(weights);
            } else if (linecount % 4 == 1) {
                // Redundant in our model, but they encode the
                // number of outputs so we have to read them in.
                m_fwd_weights->m_conv_biases.emplace_back(weights);
            } else if (linecount % 4 == 2) {
				modify_bn_scale_factor(weights);
                m_fwd_weights->m_batchnorm_means.emplace_back(weights);
            } else if (linecount % 4 == 3) {
				modify_bn_scale_factor(weights);
                process_bn_var(weights);
                m_fwd_weights->m_batchnorm_stddevs.emplace_back(weights);
            }
        } else {
            switch (linecount - plain_conv_wts) {
                case  0: m_fwd_weights->m_conv_pol_w = std::move(weights); break;
                case  1: m_fwd_weights->m_conv_pol_b = std::move(weights); break;
                case  2: modify_bn_scale_factor(weights);
                         std::copy(cbegin(weights), cend(weights),
                                   begin(m_bn_pol_w1)); break;
                case  3: modify_bn_scale_factor(weights);
                         process_bn_var(weights);
                         std::copy(cbegin(weights), cend(weights),
                                   begin(m_bn_pol_w2)); break;
                case  4:
/*
                         if (weights.size() != OUTPUTS_POLICY
                                               * NUM_INTERSECTIONS
                                               * POTENTIAL_MOVES) {
                             myprintf("The weights file is not for %dx%d boards.\n",
                                      BOARD_SIZE, BOARD_SIZE);
                             return {0, 0};
                         }
*/
//                       std::copy(cbegin(weights), cend(weights),
//                                 begin(m_ip_pol_w)); break;
                         m_conv2_pol_w = std::move(weights); break;
                case  5:
//                       std::copy(cbegin(weights), cend(weights),
//                                 begin(m_ip_pol_b)); break;
                         m_conv2_pol_b = std::move(weights); break;
                case  6: m_fwd_weights->m_conv_val_w = std::move(weights); break;
                case  7: m_fwd_weights->m_conv_val_b = std::move(weights); break;
                case  8: modify_bn_scale_factor(weights);
                         std::copy(cbegin(weights), cend(weights),
                                   begin(m_bn_val_w1)); break;
                case  9: modify_bn_scale_factor(weights);
                         process_bn_var(weights);
                         std::copy(cbegin(weights), cend(weights),
                                   begin(m_bn_val_w2)); break;
                case 10: std::copy(cbegin(weights), cend(weights),
                                   begin(m_ip1_val_w)); break;
                case 11: std::copy(cbegin(weights), cend(weights),
                                   begin(m_ip1_val_b)); break;
                case 12: std::copy(cbegin(weights), cend(weights),
                                   begin(m_ip2_val_w)); break;
                case 13: std::copy(cbegin(weights), cend(weights),
                                   begin(m_ip2_val_b)); break;
            }
        }
        linecount++;
    }
//  process_bn_var(m_bn_pol_w2);
//  process_bn_var(m_bn_val_w2);
	myprintf("num=%d...linecount=%d\n",num,linecount);

    return {channels, static_cast<int>(residual_blocks)};
}

std::pair<int, int> Network::load_network_file(const std::string& filename) {
    // gzopen supports both gz and non-gz files, will decompress
    // or just read directly as needed.
    auto gzhandle = gzopen(filename.c_str(), "rb");
    if (gzhandle == nullptr) {
        myprintf("Could not open weights file: %s\n", filename.c_str());
        return {0, 0};
    }
    // Stream the gz file in to a memory buffer stream.
    auto buffer = std::stringstream{};
    constexpr auto chunkBufferSize = 64 * 1024;
    std::vector<char> chunkBuffer(chunkBufferSize);
    while (true) {
        auto bytesRead = gzread(gzhandle, chunkBuffer.data(), chunkBufferSize);
        if (bytesRead == 0) break;
        if (bytesRead < 0) {
            myprintf("Failed to decompress or read: %s\n", filename.c_str());
            gzclose(gzhandle);
            return {0, 0};
        }
        assert(bytesRead <= chunkBufferSize);
        buffer.write(chunkBuffer.data(), bytesRead);
    }
    gzclose(gzhandle);

    // Read format version
    auto line = std::string{};
    auto format_version = -1;
    if (std::getline(buffer, line)) {
        auto iss = std::stringstream{line};
        // First line is the file format version id
        iss >> format_version;
        if (iss.fail() || (format_version != 2)) {
            myprintf("Weights file is the wrong version.\n");
            return {0, 0};
        } else {
            // Version 2 networks are identical to v1, except
            // that they return the value for black instead of
            // the player to move. This is used by ELF Open Go.

            if (format_version == 2) {
                m_value_head_not_stm = true;
            } else {
                m_value_head_not_stm = false;
            }

            return load_v1_network(buffer);
        }
    }
    return {0, 0};
}
/*
std::unique_ptr<ForwardPipe>&& Network::init_net(int channels,
    std::unique_ptr<ForwardPipe>&& pipe) {

    pipe->initialize(channels);
    pipe->push_weights(WINOGRAD_ALPHA, INPUT_CHANNELS, channels, m_fwd_weights);

    return std::move(pipe);
}
*/
std::unique_ptr<ForwardPipe> Network::init_net(int channels,
    std::unique_ptr<ForwardPipe> pipe) {

    pipe->initialize(channels);
    pipe->push_weights(WINOGRAD_ALPHA, INPUT_CHANNELS, channels, m_fwd_weights);
    return pipe;
}

#ifdef USE_HALF
void Network::select_precision(int channels) {
    if (cfg_precision == precision_t::AUTO) {
        auto score_fp16 = float{-1.0};
        auto score_fp32 = float{-1.0};

        myprintf("Initializing OpenCL (autodetecting precision).\n");

        // Setup fp16 here so that we can see if we can skip autodetect.
        // However, if fp16 sanity check fails we will return a fp32 and pray it works.
        auto fp16_net = std::make_unique<OpenCLScheduler<half_float::half>>();
        if (!fp16_net->needs_autodetect()) {
            try {
                myprintf("OpenCL: using fp16/half or tensor core compute support.\n");
                m_forward = init_net(channels, std::move(fp16_net));
                benchmark_time(1); // a sanity check run
            } catch (...) {
                myprintf("OpenCL: fp16/half or tensor core failed despite driver claiming support.\n");
                myprintf("Falling back to single precision\n");
                m_forward.reset();
                m_forward = init_net(channels,
                    std::make_unique<OpenCLScheduler<float>>());
            }
            return;
        }

        // Start by setting up fp32.
        try {
            m_forward.reset();
            m_forward = init_net(channels,
                std::make_unique<OpenCLScheduler<float>>());
            score_fp32 = benchmark_time(100);
        } catch (...) {
            // empty - if exception thrown just throw away fp32 net
        }

        // Now benchmark fp16.
        try {
            m_forward.reset();
            m_forward = init_net(channels, std::move(fp16_net));
            score_fp16 = benchmark_time(100);
        } catch (...) {
            // empty - if exception thrown just throw away fp16 net
        }

        if (score_fp16 < 0.0f && score_fp32 < 0.0f) {
            myprintf("Both single precision and half precision failed to run.\n");
            throw std::runtime_error("Failed to initialize net.");
        } else if (score_fp16 < 0.0f) {
            myprintf("Using OpenCL single precision (half precision failed to run).\n");
            m_forward.reset();
            m_forward = init_net(channels,
                std::make_unique<OpenCLScheduler<float>>());
        } else if (score_fp32 < 0.0f) {
            myprintf("Using OpenCL half precision (single precision failed to run).\n");
        } else if (score_fp32 * 1.05f > score_fp16) {
            myprintf("Using OpenCL single precision (less than 5%% slower than half).\n");
            m_forward.reset();
            m_forward = init_net(channels,
                std::make_unique<OpenCLScheduler<float>>());
        } else {
            myprintf("Using OpenCL half precision (at least 5%% faster than single).\n");
        }
        return;
    } else if (cfg_precision == precision_t::SINGLE) {
        myprintf("Initializing OpenCL (single precision).\n");
        m_forward = init_net(channels,
            std::make_unique<OpenCLScheduler<float>>());
        return;
    } else if (cfg_precision == precision_t::HALF) {
        myprintf("Initializing OpenCL (half precision).\n");
        m_forward = init_net(channels,
            std::make_unique<OpenCLScheduler<half_float::half>>());
        return;
    }
}
#endif

void Network::initialize(int /*playouts*/, const std::string & weightsfile) {
#ifdef USE_BLAS
#ifndef __APPLE__
#ifdef USE_OPENBLAS
    openblas_set_num_threads(1);
    myprintf("BLAS Core: %s\n", openblas_get_corename());
#endif
#ifdef USE_MKL
    //mkl_set_threading_layer(MKL_THREADING_SEQUENTIAL);
    mkl_set_num_threads(1);
    MKLVersion Version;
    mkl_get_version(&Version);
    myprintf("BLAS core: MKL %s\n", Version.Processor);
#endif
#endif
#else
    myprintf("BLAS Core: built-in Eigen %d.%d.%d library.\n",
             EIGEN_WORLD_VERSION, EIGEN_MAJOR_VERSION, EIGEN_MINOR_VERSION);
#endif

    m_fwd_weights = std::make_shared<ForwardPipeWeights>();
/*
    // Make a guess at a good size as long as the user doesn't
    // explicitly set a maximum memory usage.
    m_nncache.set_size_from_playouts(playouts);

    // Prepare symmetry table
    for (auto s = 0; s < NUM_SYMMETRIES; ++s) {
        for (auto v = 0; v < NUM_INTERSECTIONS; ++v) {
            const auto newvtx =
                get_symmetry({v % BOARD_SIZE, v / BOARD_SIZE}, s);
            symmetry_nn_idx_table[s][v] =
                (newvtx.second * BOARD_SIZE) + newvtx.first;
            assert(symmetry_nn_idx_table[s][v] >= 0
                   && symmetry_nn_idx_table[s][v] < NUM_INTERSECTIONS);
        }
    }
*/
    // Load network from file
    size_t channels, residual_blocks;
    std::tie(channels, residual_blocks) = load_network_file(weightsfile);
    if (channels == 0) {
        exit(EXIT_FAILURE);
    }

    auto weight_index = size_t{0};
    // Input convolution
    // Winograd transform convolution weights
    m_fwd_weights->m_conv_weights[weight_index] =
        winograd_transform_f(m_fwd_weights->m_conv_weights[weight_index],
                             channels, INPUT_CHANNELS);
    weight_index++;

    // Residual block convolutions
    for (auto i = size_t{0}; i < residual_blocks * 2; i++) {
        m_fwd_weights->m_conv_weights[weight_index] =
            winograd_transform_f(m_fwd_weights->m_conv_weights[weight_index],
                                 channels, channels);
        weight_index++;
    }

    // Biases are not calculated and are typically zero but some networks might
    // still have non-zero biases.
    // Move biases to batchnorm means to make the output match without having
    // to separately add the biases.
    auto bias_size = m_fwd_weights->m_conv_biases.size();
    for (auto i = size_t{0}; i < bias_size; i++) {
        auto means_size = m_fwd_weights->m_batchnorm_means[i].size();
        for (auto j = size_t{0}; j < means_size; j++) {
            m_fwd_weights->m_batchnorm_means[i][j] -= m_fwd_weights->m_conv_biases[i][j];
            m_fwd_weights->m_conv_biases[i][j] = 0.0f;
        }
    }

    for (auto i = size_t{0}; i < m_bn_val_w1.size(); i++) {
        m_bn_val_w1[i] -= m_fwd_weights->m_conv_val_b[i];
        m_fwd_weights->m_conv_val_b[i] = 0.0f;
    }

    for (auto i = size_t{0}; i < m_bn_pol_w1.size(); i++) {
        m_bn_pol_w1[i] -= m_fwd_weights->m_conv_pol_b[i];
        m_fwd_weights->m_conv_pol_b[i] = 0.0f;
    }

#ifdef USE_OPENCL
    if (cfg_cpu_only) {
        myprintf("Initializing CPU-only evaluation.\n");
        m_forward = init_net(channels, std::make_unique<CPUPipe>());
    } else {
#ifdef USE_OPENCL_SELFCHECK
        // initialize CPU reference first, so that we can self-check
        // when doing fp16 vs. fp32 detections
        m_forward_cpu = init_net(channels, std::make_unique<CPUPipe>());
#endif
#ifdef USE_HALF
        // HALF support is enabled, and we are using the GPU.
        // Select the precision to use at runtime.
        select_precision(channels);
#else
        myprintf("Initializing OpenCL (single precision).\n");
        m_forward = init_net(channels,
                             std::make_unique<OpenCLScheduler<float>>());
#endif
    }

#else //!USE_OPENCL
    myprintf("Initializing CPU-only evaluation.\n");
    m_forward = init_net(channels, std::make_unique<CPUPipe>());
#endif

    // Need to estimate size before clearing up the pipe.
    get_estimated_size();
    m_fwd_weights.reset();
}

template<unsigned int inputs,
         unsigned int outputs,
         bool ReLU,
         size_t W>
std::vector<float> innerproduct(const std::vector<float>& input,
                                const std::array<float, W>& weights,
                                const std::array<float, outputs>& biases) {
    std::vector<float> output(outputs);

#ifdef USE_BLAS
    cblas_sgemv(CblasRowMajor, CblasNoTrans,
                // M     K
                outputs, inputs,
                1.0f, &weights[0], inputs,
                &input[0], 1,
                0.0f, &output[0], 1);
#else
    EigenVectorMap<float> y(output.data(), outputs);
    y.noalias() =
        ConstEigenMatrixMap<float>(weights.data(),
                                   inputs,
                                   outputs).transpose()
        * ConstEigenVectorMap<float>(input.data(), inputs);
#endif
    const auto lambda_ReLU = [](const auto val) { return (val > 0.0f) ?
                                                          val : 0.0f; };
    for (unsigned int o = 0; o < outputs; o++) {
        auto val = biases[o] + output[o];
        if (ReLU) {
            val = lambda_ReLU(val);
        }
        output[o] = val;
    }

    return output;
}

template <size_t spatial_size>
void batchnorm(const size_t channels,
               std::vector<float>& data,
               const float* const means,
               const float* const stddivs,
               const float* const eltwise = nullptr) {
    const auto lambda_ReLU = [](const auto val) { return (val > 0.0f) ?
                                                          val : 0.0f; };
    for (auto c = size_t{0}; c < channels; ++c) {
        const auto mean = means[c];
        const auto scale_stddiv = stddivs[c];
        const auto arr = &data[c * spatial_size];

        if (eltwise == nullptr) {
            // Classical BN
            for (auto b = size_t{0}; b < spatial_size; b++) {
                arr[b] = lambda_ReLU(scale_stddiv * (arr[b] - mean));
            }
        } else {
            // BN + residual add
            const auto res = &eltwise[c * spatial_size];
            for (auto b = size_t{0}; b < spatial_size; b++) {
                arr[b] = lambda_ReLU((scale_stddiv * (arr[b] - mean)) + res[b]);
            }
        }
    }
}

#ifdef USE_OPENCL_SELFCHECK
/*
void Network::compare_net_outputs(const Netresult& data,
                                  const Netresult& ref) {
    // Calculates L2-norm between data and ref.
    constexpr auto max_error = 0.2f;

    auto error = 0.0f;

    for (auto idx = size_t{0}; idx < data.policy.size(); ++idx) {
        const auto diff = data.policy[idx] - ref.policy[idx];
        error += diff * diff;
    }
    const auto diff_pass = data.policy_pass - ref.policy_pass;
    const auto diff_winrate = data.winrate - ref.winrate;
    error += diff_pass * diff_pass;
    error += diff_winrate * diff_winrate;

    error = std::sqrt(error);

    if (error > max_error || std::isnan(error)) {
        printf("Error in OpenCL calculation: Update your device's OpenCL drivers "
               "or reduce the amount of games played simultaneously.\n");
        throw std::runtime_error("OpenCL self-check mismatch.");
    }
}
*/
template<typename T>
T relative_difference(T a, T b) {
    // Handle NaN
    if (std::isnan(a) || std::isnan(b)) {
        return std::numeric_limits<T>::max();
    }
    // Handle sign difference
    if (((a < 0) != (b < 0)) && (a != 0) && (b != 0)) {
        return std::numeric_limits<T>::max();
    }
    a = std::fabs(a);
    b = std::fabs(b);

    // Handle underflow
    constexpr float small_number = 1e-3f;
    a = std::max(a, small_number);
    b = std::max(b, small_number);

    return std::max(fabs((a - b) / a), fabs((a - b) / b));
}

//void compare_net_outputs(std::vector<float>& data,
//                         std::vector<float>& ref) {
void Network::compare_net_outputs(std::vector<scored_node>& data,
                                  std::vector<scored_node>& ref) {
    // We accept an error up to 5%, but output values
    // smaller than 1/1000th are "rounded up" for the comparison.
    constexpr float relative_error = 5e-2f;
    for (auto idx = size_t{0}; idx < data.size(); ++idx) {
        auto err = relative_difference(data[idx].first, ref[idx].first);
        if (err > relative_error) {
            printf("Error in OpenCL calculation: expected %f got %f "
                   "(error=%f%%)\n", ref[idx].first, data[idx].first, err * 100.0);
            printf("Update your GPU drivers or reduce the amount of games "
                   "played simultaneously.\n");
            throw std::runtime_error("OpenCL self-check mismatch.");
        }
    }
}
#endif

std::vector<float> softmax(const std::vector<float>& input,
                           const float temperature = 1.0f) {
    auto output = std::vector<float>{};
    output.reserve(input.size());

    const auto alpha = *std::max_element(cbegin(input), cend(input));
    auto denom = 0.0f;

    for (const auto in_val : input) {
        auto val = std::exp((in_val - alpha) / temperature);
        denom += val;
        output.push_back(val);
    }

    for (auto& out : output) {
        out /= denom;
    }

    return output;
}
/*
bool Network::probe_cache(const GameState* const state,
                          Network::Netresult& result) {
    if (m_nncache.lookup(state->board.get_hash(), result)) {
        return true;
    }
    // If we are not generating a self-play game, try to find
    // symmetries if we are in the early opening.
    if (!cfg_noise && !cfg_random_cnt
        && state->get_movenum()
           < (state->get_timecontrol().opening_moves(BOARD_SIZE) / 2)) {
        for (auto sym = 0; sym < Network::NUM_SYMMETRIES; ++sym) {
            if (sym == Network::IDENTITY_SYMMETRY) {
                continue;
            }
            const auto hash = state->get_symmetry_hash(sym);
            if (m_nncache.lookup(hash, result)) {
                decltype(result.policy) corrected_policy;
                for (auto idx = size_t{0}; idx < NUM_INTERSECTIONS; ++idx) {
                    const auto sym_idx = symmetry_nn_idx_table[sym][idx];
                    corrected_policy[idx] = result.policy[sym_idx];
                }
                result.policy = std::move(corrected_policy);
                return true;
            }
        }
    }
    return false;
}
*/
Network::Netresult_old Network::get_output(
    NNPlanes & planes, const bool force_selfcheck) {
//    Netresult result;
    Netresult_old result;
/*
    if (state->board.get_boardsize() != BOARD_SIZE) {
        return result;
    }

    if (read_cache) {
        // See if we already have this in the cache.
        if (probe_cache(state, result)) {
            return result;
        }
    }
*/
/*
    if (ensemble == DIRECT) {
        assert(symmetry >= 0 && symmetry < NUM_SYMMETRIES);
        result = get_output_internal(state, symmetry);
    } else if (ensemble == AVERAGE) {
        assert(symmetry == -1);
        for (auto sym = 0; sym < NUM_SYMMETRIES; ++sym) {
            auto tmpresult = get_output_internal(state, sym);
            result.winrate +=
                tmpresult.winrate / static_cast<float>(NUM_SYMMETRIES);
            result.policy_pass +=
                tmpresult.policy_pass / static_cast<float>(NUM_SYMMETRIES);

            for (auto idx = size_t{0}; idx < NUM_INTERSECTIONS; idx++) {
                result.policy[idx] +=
                    tmpresult.policy[idx] / static_cast<float>(NUM_SYMMETRIES);
            }
        }
    } else {
        assert(ensemble == RANDOM_SYMMETRY);
        assert(symmetry == -1);
*/
//        const auto rand_sym = Random::get_Rng().randfix<NUM_SYMMETRIES>();
        result = get_output_internal(planes);
#ifdef USE_OPENCL_SELFCHECK
        // Both implementations are available, self-check the OpenCL driver by
        // running both with a probability of 1/2000.
        // selfcheck is done here because this is the only place NN
        // evaluation is done on actual gameplay.
        if (m_forward_cpu != nullptr
            && (force_selfcheck || Random::get_Rng().randfix<SELFCHECK_PROBABILITY>() == 0)
        ) {
            auto result_ref = get_output_internal(planes, true);
            compare_net_outputs(result.first, result_ref.first);
        }
#else
        (void)force_selfcheck;
#endif
/*
    }
*/
/*
    // v2 format (ELF Open Go) returns black value, not stm
    if (m_value_head_not_stm) {
        if (state->board.get_to_move() == FastBoard::WHITE) {
            result.winrate = 1.0f - result.winrate;
        }
    }

    if (write_cache) {
        // Insert result into cache.
        m_nncache.insert(state->board.get_hash(), result);
    }
*/
    return result;
}

Network::Netresult_old Network::get_output_internal(
    NNPlanes & planes, bool selfcheck) {
//    assert(symmetry >= 0 && symmetry < NUM_SYMMETRIES);
    constexpr auto width = BOARD_SIZE;
    constexpr auto height = BOARD_SIZE;

//    const auto input_data = gather_features(state, symmetry);
    std::vector<float> input_data;
    // Data layout is input_data[(c * height + h) * width + w]
    input_data.reserve(INPUT_CHANNELS * width * height);

    for (int c = 0; c < INPUT_CHANNELS; ++c) {
        for (int h = 0; h < height; ++h) {
            for (int w = 0; w < width; ++w) {
                input_data.emplace_back(net_t(planes[c][h*B_SIZE + w]));
            }
        }
    }
	if ( 0 ) { float s=0; for (size_t i=0; i<input_data.size(); i++) s += input_data[i]; myprintf("input_data.size()=%d,sum=%f\n",input_data.size(),s); }

    std::vector<float> policy_data(OUTPUTS_POLICY * width * height);
    std::vector<float> value_data(OUTPUTS_VALUE * width * height);
#ifdef USE_OPENCL_SELFCHECK
    if (selfcheck) {
        m_forward_cpu->forward(input_data, policy_data, value_data);
    } else {
        m_forward->forward(input_data, policy_data, value_data);
    }
#else
    m_forward->forward(input_data, policy_data, value_data);
    (void) selfcheck;
#endif

	if ( 0 ) { float s=0; for (size_t i=0; i<policy_data.size(); i++) s += policy_data[i]; myprintf("policy_data.size()=%d,sum=%f\n",policy_data.size(),s); }
	if ( 0 ) { float s=0; for (size_t i=0; i<value_data.size();  i++) s += value_data[i];  myprintf("value_data.size() =%d,sum=%f\n",value_data.size(),s); }

    // Get the moves
//    batchnorm<NUM_INTERSECTIONS>(OUTPUTS_POLICY, policy_data,  m_bn_pol_w1.data(), m_bn_pol_w2.data());
    batchnorm<B_AREA>(OUTPUTS_POLICY, policy_data,  m_bn_pol_w1.data(), m_bn_pol_w2.data());
//    const auto policy_out =
//        innerproduct<OUTPUTS_POLICY * NUM_INTERSECTIONS, POTENTIAL_MOVES, false>(
//            policy_data, m_ip_pol_w, m_ip_pol_b);
//    const auto outputs = softmax(policy_out, cfg_softmax_temp);

    std::vector<float> policy_out(POLICY_OUT_NUM);
    convolve<1>(139, policy_data, m_conv2_pol_w, m_conv2_pol_b, policy_out);	// 9*9*139 = 11259
    const auto outputs = softmax(policy_out, cfg_softmax_temp);
//    softmax(policy_out, softmax_data, cfg_softmax_temp);
//    std::vector<float>& outputs = softmax_data;

    // Now get the value
//    batchnorm<NUM_INTERSECTIONS>(OUTPUTS_VALUE, value_data, m_bn_val_w1.data(), m_bn_val_w2.data());
    batchnorm<B_AREA>(OUTPUTS_VALUE, value_data, m_bn_val_w1.data(), m_bn_val_w2.data());
    const auto winrate_data =
//        innerproduct<OUTPUTS_VALUE * NUM_INTERSECTIONS, VALUE_LAYER, true>( value_data, m_ip1_val_w, m_ip1_val_b);
        innerproduct<OUTPUTS_VALUE * B_AREA, VALUE_LAYER, true>( value_data, m_ip1_val_w, m_ip1_val_b);
    const auto winrate_out =
        innerproduct<VALUE_LAYER, 1, false>(winrate_data, m_ip2_val_w, m_ip2_val_b);

    // Map TanH output range [-1..1] to [0..1] range
//    const auto winrate = (1.0f + std::tanh(winrate_out[0])) / 2.0f;
    const auto winrate_sig = std::tanh(winrate_out[0]);
/*
    Netresult result;

    for (auto idx = size_t{0}; idx < NUM_INTERSECTIONS; idx++) {
        const auto sym_idx = symmetry_nn_idx_table[symmetry][idx];
        result.policy[sym_idx] = outputs[idx];
    }

    result.policy_pass = outputs[NUM_INTERSECTIONS];
    result.winrate = winrate;
*/
    std::vector<scored_node> result;
    for (auto idx = size_t{0}; idx < outputs.size(); idx++) {
//      if ( idx < 100 ) myprintf("%3d:%f\n",idx,outputs[idx]);
//    myprintf("done tanh....%d,outputs.size()=%d\n",__LINE__,outputs.size());
//		float v = outputs[idx];
//    myprintf("done tanh....%d,v=%f\n",__LINE__,v);
//        result.emplace_back(v, idx);
        result.emplace_back(outputs[idx], idx);
    }
//  myprintf("done net calc\n");

    return std::make_pair(result, winrate_sig);
//    return result;
}
/*
void Network::show_heatmap(const FastState* const state,
                           const Netresult& result,
                           const bool topmoves) {
    std::vector<std::string> display_map;
    std::string line;

    for (unsigned int y = 0; y < BOARD_SIZE; y++) {
        for (unsigned int x = 0; x < BOARD_SIZE; x++) {
            auto policy = 0;
            const auto vertex = state->board.get_vertex(x, y);
            if (state->board.get_state(vertex) == FastBoard::EMPTY) {
                policy = result.policy[y * BOARD_SIZE + x] * 1000;
            }

            line += boost::str(boost::format("%3d ") % policy);
        }

        display_map.push_back(line);
        line.clear();
    }

    for (int i = display_map.size() - 1; i >= 0; --i) {
        myprintf("%s\n", display_map[i].c_str());
    }
    const auto pass_policy = int(result.policy_pass * 1000);
    myprintf("pass: %d\n", pass_policy);
    myprintf("winrate: %f\n", result.winrate);

    if (topmoves) {
        std::vector<Network::PolicyVertexPair> moves;
        for (auto i=0; i < NUM_INTERSECTIONS; i++) {
            const auto x = i % BOARD_SIZE;
            const auto y = i / BOARD_SIZE;
            const auto vertex = state->board.get_vertex(x, y);
            if (state->board.get_state(vertex) == FastBoard::EMPTY) {
                moves.emplace_back(result.policy[i], vertex);
            }
        }
        moves.emplace_back(result.policy_pass, FastBoard::PASS);

        std::stable_sort(rbegin(moves), rend(moves));

        auto cum = 0.0f;
        for (const auto& move : moves) {
            if (cum > 0.85f || move.first < 0.01f) break;
            myprintf("%1.3f (%s)\n",
                    move.first,
                    state->board.move_to_text(move.second).c_str());
            cum += move.first;
        }
    }
}

void Network::fill_input_plane_pair(const FullBoard& board,
                                    std::vector<float>::iterator black,
                                    std::vector<float>::iterator white,
                                    const int symmetry) {
    for (auto idx = 0; idx < NUM_INTERSECTIONS; idx++) {
        const auto sym_idx = symmetry_nn_idx_table[symmetry][idx];
        const auto x = sym_idx % BOARD_SIZE;
        const auto y = sym_idx / BOARD_SIZE;
        const auto color = board.get_state(x, y);
        if (color == FastBoard::BLACK) {
            black[idx] = float(true);
        } else if (color == FastBoard::WHITE) {
            white[idx] = float(true);
        }
    }
}
*/
std::vector<float> Network::gather_features(const GameState* const /*state*/,
                                            const int /*symmetry*/) {
myprintf("gather_features() To do!\n");
//    assert(symmetry >= 0 && symmetry < NUM_SYMMETRIES);
    auto input_data = std::vector<float>(INPUT_CHANNELS * NUM_INTERSECTIONS);
#if 0
    const auto to_move = state->get_to_move();
    const auto blacks_move = to_move == FastBoard::BLACK;

    const auto black_it = blacks_move ?
                          begin(input_data) :
                          begin(input_data) + INPUT_MOVES * NUM_INTERSECTIONS;
    const auto white_it = blacks_move ?
                          begin(input_data) + INPUT_MOVES * NUM_INTERSECTIONS :
                          begin(input_data);
    const auto to_move_it = blacks_move ?
        begin(input_data) + 2 * INPUT_MOVES * NUM_INTERSECTIONS :
        begin(input_data) + (2 * INPUT_MOVES + 1) * NUM_INTERSECTIONS;

    const auto moves = std::min<size_t>(state->get_movenum() + 1, INPUT_MOVES);
    // Go back in time, fill history boards
    for (auto h = size_t{0}; h < moves; h++) {
        // collect white, black occupation planes
        fill_input_plane_pair(state->get_past_board(h),
                              black_it + h * NUM_INTERSECTIONS,
                              white_it + h * NUM_INTERSECTIONS,
                              symmetry);
    }

    std::fill(to_move_it, to_move_it + NUM_INTERSECTIONS, float(true));
#endif
    return input_data;
}
/*
std::pair<int, int> Network::get_symmetry(const std::pair<int, int>& vertex,
                                          const int symmetry,
                                          const int board_size) {
    auto x = vertex.first;
    auto y = vertex.second;
    assert(x >= 0 && x < board_size);
    assert(y >= 0 && y < board_size);
    assert(symmetry >= 0 && symmetry < NUM_SYMMETRIES);

    if ((symmetry & 4) != 0) {
        std::swap(x, y);
    }

    if ((symmetry & 2) != 0) {
        x = board_size - x - 1;
    }

    if ((symmetry & 1) != 0) {
        y = board_size - y - 1;
    }

    assert(x >= 0 && x < board_size);
    assert(y >= 0 && y < board_size);
    assert(symmetry != IDENTITY_SYMMETRY || vertex == std::make_pair(x, y));
    return {x, y};
}
*/
size_t Network::get_estimated_size() {
    if (estimated_size != 0) {
        return estimated_size;
    }
    auto result = size_t{0};

    const auto lambda_vector_size =  [](const std::vector<std::vector<float>> &v) {
        auto result = size_t{0};
        for (auto it = begin(v); it != end(v); ++it) {
            result += it->size() * sizeof(float);
        }
        return result;
    };

    result += lambda_vector_size(m_fwd_weights->m_conv_weights);
    result += lambda_vector_size(m_fwd_weights->m_conv_biases);
    result += lambda_vector_size(m_fwd_weights->m_batchnorm_means);
    result += lambda_vector_size(m_fwd_weights->m_batchnorm_stddevs);

    result += m_fwd_weights->m_conv_pol_w.size() * sizeof(float);
    result += m_fwd_weights->m_conv_pol_b.size() * sizeof(float);

    // Policy head
    result += OUTPUTS_POLICY * sizeof(float); // m_bn_pol_w1
    result += OUTPUTS_POLICY * sizeof(float); // m_bn_pol_w2
    result += OUTPUTS_POLICY * NUM_INTERSECTIONS
                             * POTENTIAL_MOVES * sizeof(float); //m_ip_pol_w
    result += POTENTIAL_MOVES * sizeof(float); // m_ip_pol_b

    // Value head
    result += m_fwd_weights->m_conv_val_w.size() * sizeof(float);
    result += m_fwd_weights->m_conv_val_b.size() * sizeof(float);
    result += OUTPUTS_VALUE * sizeof(float); // m_bn_val_w1
    result += OUTPUTS_VALUE * sizeof(float); // m_bn_val_w2

    result += OUTPUTS_VALUE * NUM_INTERSECTIONS
                            * VALUE_LAYER * sizeof(float); // m_ip1_val_w
    result += VALUE_LAYER * sizeof(float);  // m_ip1_val_b

    result += VALUE_LAYER * sizeof(float); // m_ip2_val_w
    result += sizeof(float); // m_ip2_val_b
    return estimated_size = result;
}

size_t Network::get_estimated_cache_size() {
    return m_nncache.get_estimated_size();
}

void Network::nncache_resize(int max_count) {
    return m_nncache.resize(max_count);
}

Network::Netresult_old Network::get_scored_moves_yss_zero(float data[][B_SIZE][B_SIZE]) {
    Netresult_old result;
    NNPlanes planes;
    gather_features_yss_zero(planes, data);

	result = get_output( planes );
//    result = get_scored_moves_internal(NULL, planes, 0);
/*
	auto net_eval = result.second;
//    myprintf("eval=%f\n",net_eval);
    std::vector<Network::scored_node> nodelist;

    auto legal_sum = 0.0f;
    for (const auto& node : result.first) {
        legal_sum += node.first;
        auto vertex = node.second;
//	    if ( vertex < 100 || vertex > result.first.size()-100 ) myprintf("%4d,%f\n",vertex,node.first);
//	    if ( vertex < 100 ) myprintf("%4d,%f\n",vertex,node.first);
    }
//  myprintf("legal_sum=%f\n",legal_sum);
*/
    return result;
}

void Network::gather_features_yss_zero(NNPlanes & planes, float data[][B_SIZE][B_SIZE]) {
//    myprintf("gather_features_yss_zero()\n");

    planes.resize(INPUT_CHANNELS);

    int c,z;
    for (c=0;c<INPUT_CHANNELS;c++) {
//		myprintf("%d\n",c);
		for (z=0;z<81;z++) {
			int y = z / 9;
			int x = z - y*9;
			planes[c][z] = data[c][y][x];
//			myprintf("%d,",planes[c][z]);
//			if ( ((z+1)%9)==0 ) myprintf("\n");
		}
	}

	return;
}