text:
// Copyright (c) 2021 Graphcore Ltd. All rights reserved.

#include "popnn/CTCInference.hpp"

#include "CTCInferenceConnection.hpp"
#include "CTCInferencePlan.hpp"
#include "CTCPlanInternal.hpp"

#include <poplar/CSRFunctions.hpp>
#include <poplar/Graph.hpp>
#include <poplar/Tensor.hpp>

#include <poplibs_support/CTCInferenceDefs.hpp>
#include <poplibs_support/LogArithmetic.hpp>
#include <poplibs_support/Tracepoint.hpp>
#include <poplibs_support/Visitor.hpp>
#include <poplibs_support/logging.hpp>
#include <popnn/LogSoftmax.hpp>
#include <popops/Cast.hpp>
#include <popops/ElementWise.hpp>
#include <popops/Encoding.hpp>
#include <popops/Expr.hpp>
#include <popops/Loop.hpp>
#include <popops/Reduce.hpp>
#include <poputil/OptionParsing.hpp>
#include <poputil/TileMapping.hpp>
#include <poputil/exceptions.hpp>

#include <boost/optional.hpp>

using namespace poplar;
using namespace poplar::program;
using namespace poplibs_support;
using namespace popops;
using namespace popops::expr;
using namespace poputil;

template <unsigned size> using Slice = std::array<std::size_t, size>;
enum class PartitionType {
  BATCH,
  BATCH_ENTRY,
  COPY,
  EXTEND,
  MERGE,
  SELECT_COPY,
  SELECT_EXTEND,
  SORT,
  SORT_REDUCE,
  OUTPUT
};

namespace {

using TempTensors = popnn::ctc_infer::TempTensors;
using BeamTensors = popnn::ctc_infer::BeamTensors;
using SortTensors = popnn::ctc_infer::SortTensors;
static constexpr auto voidSymbol = popnn::ctc_infer::voidSymbol;
static constexpr auto invalidSymbol = popnn::ctc_infer::invalidSymbol;

void mapDataInputAccordingToPlan(Graph &graph, const Tensor &tensor,
                                 const popnn::ctc::InferencePlan &plan) {
  // Map the data input according to the plan, but the innermost dimension
  // isn't really compatible with the plan, as it is the number of classes
  // whereas we planned for batchEntry partitions.
  // Choose to split the time dimension as much as possible over the combined
  // time and batchEntry partitions.  This avoids splitting the innermost
  // dimension which would result in increased exchange code size.
  const auto batchSize = tensor.dim(0);
  const unsigned timeSize = tensor.dim(2);
  const auto numClasses = tensor.dim(3);

  const auto numNonBatchPartitions =
      plan.parallel.time * plan.batchEntryPartitions();
  const auto remappedTimePartitions = std::min(numNonBatchPartitions, timeSize);
  const auto typeSize = graph.getTarget().getTypeSize(tensor.elementType());

  const auto timePartitionSize = [&]() {
    // Minimum result to map all the time slices onto the tiles within the plan
    // without splitting the innermost dimension
    auto minTimePartitionSize = ceildiv(timeSize, remappedTimePartitions);
    // Ensure that there are always a multiple of 4 bytes per tile to avoid
    // costly exchange.
    // Trialling timePartitionSize+0, +1, +2, +3 must produce a result divisible
    // by 4, as we will hit timePartitionSize+N as a multiple of 4 itself.
    for (unsigned i = 0; i < 4; i++) {
      const auto remainder = (typeSize * numClasses * minTimePartitionSize) % 4;
      if (remainder == 0) {
        break;
      }
      minTimePartitionSize++;
    }
    return minTimePartitionSize;
  }();
  assert((typeSize * timePartitionSize * numClasses) % 4 == 0);

  for (unsigned batch = 0; batch < plan.parallel.batch; batch++) {
    for (unsigned time = 0; time < remappedTimePartitions; time++) {
      const auto timeBegin = time * timePartitionSize;
      if (timeBegin < timeSize) {
        auto tile = plan.getTile(batch, time);
        auto b = plan.partitionBatch(batchSize, batch);
        const auto timeEnd = std::min(timeSize, (time + 1) * timePartitionSize);

        graph.setTileMapping(tensor.slice({b.begin(), 0, timeBegin, 0},
                                          {b.end(), 1, timeEnd, numClasses}),
                             tile);
      }
    }
  }
}

using PartitionFn = Interval (popnn::ctc::InferencePlan::*)(unsigned,
                                                            unsigned) const;
using SortPartitionFn = Interval (popnn::ctc::InferencePlan::*)(unsigned,
                                                                unsigned,
                                                                unsigned) const;

PartitionFn getPartitionFunction(PartitionType type) {
  switch (type) {
  case PartitionType::BATCH:
    return &popnn::ctc::InferencePlan::partitionBatch;
  case PartitionType::BATCH_ENTRY:
    return &popnn::ctc::InferencePlan::partitionBatchEntry;
  case PartitionType::COPY:
    return &popnn::ctc::InferencePlan::partitionCopy;
  case PartitionType::EXTEND:
    return &popnn::ctc::InferencePlan::partitionExtend;
  case PartitionType::MERGE:
    return &popnn::ctc::InferencePlan::partitionMerge;
  case PartitionType::SELECT_COPY:
    return &popnn::ctc::InferencePlan::partitionSelectCopy;
  case PartitionType::SELECT_EXTEND:
    return &popnn::ctc::InferencePlan::partitionSelectExtend;
  case PartitionType::OUTPUT:
    return &popnn::ctc::InferencePlan::partitionOutput;
  case PartitionType::SORT:
  case PartitionType::SORT_REDUCE:
  default:
    POPLIB_UNREACHABLE();
  };
}

SortPartitionFn getSortPartitionFunction(PartitionType type) {
  switch (type) {
  case PartitionType::SORT:
    return &popnn::ctc::InferencePlan::partitionSort;
  case PartitionType::SORT_REDUCE:
    return &popnn::ctc::InferencePlan::partitionSortReduce;
  default:
    POPLIB_UNREACHABLE();
  };
}

void mapAccordingToPlan(Graph &graph, const Tensor &tensor,
                        PartitionType partitionType, unsigned totalPartitions,
                        const popnn::ctc::InferencePlan &plan) {
  // Map any rank 3 tensors used in this process to the correct tiles according
  // to the plan.
  assert(tensor.rank() == 3);

  const auto batchSize = tensor.dim(0);
  const auto partitionedDimSize = tensor.dim(1);
  const auto innermostDimSize = tensor.dim(2);
  const auto partitionFunction = getPartitionFunction(partitionType);

  for (unsigned batch = 0; batch < plan.parallel.batch; batch++) {
    for (unsigned partition = 0; partition < totalPartitions; partition++) {
      auto tile = plan.getTile(batch, 0, partition);
      auto b = plan.partitionBatch(batchSize, batch);
      auto p = (plan.*partitionFunction)(partitionedDimSize, partition);
      graph.setTileMapping(tensor.slice({b.begin(), p.begin(), 0},
                                        {b.end(), p.end(), innermostDimSize}),
                           tile);
    }
  }
}

void mapAccordingToPlan(Graph &graph, const Tensor &tensor,
                        PartitionType partitionType,
                        const popnn::ctc::InferencePlan &plan) {
  // Map any rank 4 tensors used in this process to the correct tiles according
  // to the plan
  assert(tensor.rank() == 4);

  const auto batchSize = tensor.dim(0);
  const auto totalPartitions = tensor.dim(1);
  const auto timeSize = tensor.dim(2);
  const auto innermostDimSize = tensor.dim(3);
  const auto partitionFunction = getPartitionFunction(partitionType);

  for (unsigned partition = 0; partition < plan.batchEntryPartitions();
       partition++) {
    for (unsigned batch = 0; batch < plan.parallel.batch; batch++) {
      for (unsigned time = 0; time < plan.parallel.time; time++) {

        auto tile = plan.getTile(batch, time, partition);
        auto b = plan.partitionBatch(batchSize, batch);
        auto p = (plan.*partitionFunction)(totalPartitions, partition);
        auto t = plan.partitionTime(timeSize, time);
        graph.setTileMapping(
            tensor.slice({b.begin(), p.begin(), t.begin(), 0},
                         {b.end(), p.end(), t.end(), innermostDimSize}),
            tile);
      }
    }
  }
}

void mapSortAccordingToPlan(Graph &graph, const Tensor &tensor,
                            PartitionType partitionType, unsigned stage,
                            unsigned groups, unsigned partitionsPerGroup,
                            const popnn::ctc::InferencePlan &plan) {

  const auto batchSize = tensor.dim(0);
  const auto partitionedDimSize = tensor.dim(1) / groups;
  const auto innermostDimSize = tensor.dim(2);
  const auto partitionFunction = getSortPartitionFunction(partitionType);

  for (unsigned batch = 0; batch < plan.parallel.batch; batch++) {
    auto b = plan.partitionBatch(batchSize, batch);
    for (unsigned group = 0; group < groups; group++) {
      const auto groupStart = group * partitionedDimSize;

      for (unsigned partition = 0; partition < partitionsPerGroup;
           partition++) {
        auto tile = plan.getTile(batch, 0, stage, group, partition);
        auto p =
            (plan.*partitionFunction)(partitionedDimSize, partition, stage);

        graph.setTileMapping(
            tensor.slice({b.begin(), groupStart + p.begin(), 0},
                         {b.end(), groupStart + p.end(), innermostDimSize}),
            tile);
      }
    }
  }
}

TempTensors createAndInitialiseTemporaryTensors(
    Graph &graph, const Tensor &dataLengths,
    const popnn::ctc::InferencePlan &plan, unsigned numClasses,
    unsigned batchSize, unsigned beamwidth, const Type &partialsType,
    Sequence &prog, const poplar::DebugContext &di) {

  TempTensors tempTensors;

  // Find the maximum time for any of the inputs in this batch so that we can
  // stop early if possible
  // Reduce doesn't support unsigned int, only int
  auto dataLengthsInt = popops::cast(graph, dataLengths, INT, prog, di);
  auto maxTimeInBatch = popops::reduce(graph, dataLengthsInt.flatten(), {0},
                                       {popops::Operation::MAX}, prog, di);
  // Cast back and add 1 as we have a dummy 1st timestep and so will count
  // 1,2,3,...maxTimeInBatch   (Loop limit = maxTimeInbatch+1)
  tempTensors.maxTimeInBatch =
      popops::map(graph, Cast(Add(_1, Const(1u)), UNSIGNED_INT),
                  {maxTimeInBatch}, prog, di);
  graph.setTileMapping(tempTensors.maxTimeInBatch, 0);

  // Add a loop variable timestep
  tempTensors.loopTimestep =
      graph.addVariable(UNSIGNED_INT, {}, {di, "loopTimestep"});
  graph.setTileMapping(tempTensors.loopTimestep, 0);

  // A per tile copy of the loop count to copy into at the start of each loop
  // pass
  tempTensors.currentTimestep = graph.addVariable(
      UNSIGNED_INT, {batchSize, plan.batchEntryPartitions(), 1},
      {di, "currentTimestep"});
  mapAccordingToPlan(graph, tempTensors.currentTimestep,
                     PartitionType::BATCH_ENTRY, plan.batchEntryPartitions(),
                     plan);

  // Data length tensor broadcast per tile
  tempTensors.dataLengths = graph.addVariable(
      UNSIGNED_INT, {batchSize, plan.batchEntryPartitions(), 1},
      {di, "dataLengths"});
  mapAccordingToPlan(graph, tempTensors.dataLengths, PartitionType::BATCH_ENTRY,
                     plan.batchEntryPartitions(), plan);
  auto dataLengthsBroadcast =
      dataLengths.expand({1, 1}).broadcast(plan.batchEntryPartitions(), 1);
  prog.add(Copy(dataLengthsBroadcast, tempTensors.dataLengths));

  // A flag to indicate if a batch entry is complete or not.  Initialised to
  // zero, set to one when the update vertex detects that the end is reached
  tempTensors.complete = graph.addVariable(
      UNSIGNED_INT, {batchSize, plan.batchEntryPartitions(), 1},
      {di, "completeFlags"});
  mapAccordingToPlan(graph, tempTensors.complete, PartitionType::BATCH_ENTRY,
                     plan.batchEntryPartitions(), plan);
  auto initialiserZero = graph.addConstant(UNSIGNED_INT, {1}, 0u, di);
  graph.setTileMapping(initialiserZero, 0);
  prog.add(
      Copy(initialiserZero.broadcast(tempTensors.complete.numElements(), 0),
           tempTensors.complete.flatten(), false, di));

  // Extend candidates
  const std::vector<std::size_t> extendCandidateShape = {
      batchSize, numClasses - 1, beamwidth};
  tempTensors.extendCandidatesPb = graph.addVariable(
      partialsType, extendCandidateShape, {di, "extendCandidatesPb"});
  tempTensors.extendCandidatesPnb = graph.addVariable(
      partialsType, extendCandidateShape, {di, "extendCandidatesPnb"});
  tempTensors.extendCandidatesPTotal = graph.addVariable(
      partialsType, extendCandidateShape, {di, "extendCandidatesPTotal"});

  tempTensors.extendCandidatesParent = graph.addVariable(
      UNSIGNED_INT, extendCandidateShape, {di, "extendCandidatesParents"});
  tempTensors.extendCandidatesAddend = graph.addVariable(
      UNSIGNED_INT, extendCandidateShape, {di, "extendCandidatesAddends"});

  mapAccordingToPlan(graph, tempTensors.extendCandidatesPb,
                     PartitionType::EXTEND, plan.parallel.extend, plan);
  mapAccordingToPlan(graph, tempTensors.extendCandidatesPnb,
                     PartitionType::EXTEND, plan.parallel.extend, plan);
  mapAccordingToPlan(graph, tempTensors.extendCandidatesPTotal,
                     PartitionType::EXTEND, plan.parallel.extend, plan);
  mapAccordingToPlan(graph, tempTensors.extendCandidatesParent,
                     PartitionType::EXTEND, plan.parallel.extend, plan);
  mapAccordingToPlan(graph, tempTensors.extendCandidatesAddend,
                     PartitionType::EXTEND, plan.parallel.extend, plan);

  // Tensors, mapped to match tiles used for the input to the Select step, to
  // hold extend candidates PTotal and addend.  Using these will reduce the copy
  // overhead
  auto makeSelectExtendCandidates = [&](const Type &type,
                                        const std::string &debugName) {
    const std::vector<std::size_t> shape = {batchSize, beamwidth,
                                            numClasses - 1};
    auto result = graph.addVariable(type, shape, {di, debugName});
    mapAccordingToPlan(graph, result, PartitionType::EXTEND,
                       plan.parallel.extend, plan);
    return result.dimShuffle({0, 2, 1});
  };
  tempTensors.selectExtendCandidatesPTotal =
      makeSelectExtendCandidates(partialsType, "selectExtendCandidatesPTotal");
  tempTensors.selectExtendCandidatesAddend =
      makeSelectExtendCandidates(UNSIGNED_INT, "selectExtendCandidatesAddend");

  // Copy candidates
  const std::vector<std::size_t> copyCandidateShape = {batchSize, beamwidth, 1};
  tempTensors.copyCandidatesPb = graph.addVariable(
      partialsType, copyCandidateShape, {di, "copyCandidatesPb"});
  tempTensors.copyCandidatesPnb = graph.addVariable(
      partialsType, copyCandidateShape, {di, "copyCandidatesPnb"});
  tempTensors.copyCandidatesPTotal = graph.addVariable(
      partialsType, copyCandidateShape, {di, "copyCandidatesPTotal"});

  tempTensors.copyCandidatesParent = graph.addVariable(
      UNSIGNED_INT, copyCandidateShape, {di, "copyCandidatesParent"});
  tempTensors.copyCandidatesAddend = graph.addVariable(
      UNSIGNED_INT, copyCandidateShape, {di, "copyCandidatesAddend"});

  mapAccordingToPlan(graph, tempTensors.copyCandidatesPb, PartitionType::COPY,
                     plan.parallel.copy, plan);
  mapAccordingToPlan(graph, tempTensors.copyCandidatesPnb, PartitionType::COPY,
                     plan.parallel.copy, plan);
  mapAccordingToPlan(graph, tempTensors.copyCandidatesPTotal,
                     PartitionType::COPY, plan.parallel.copy, plan);
  mapAccordingToPlan(graph, tempTensors.copyCandidatesParent,
                     PartitionType::COPY, plan.parallel.copy, plan);
  mapAccordingToPlan(graph, tempTensors.copyCandidatesAddend,
                     PartitionType::COPY, plan.parallel.copy, plan);

  // Merge merge candidates vectors of tensors
  tempTensors.mergeCandidatesPb.resize(beamwidth);
  tempTensors.mergeCandidatesPnb.resize(beamwidth);
  tempTensors.mergeCandidatesPTotal.resize(beamwidth);
  tempTensors.mergeCandidatesParent.resize(beamwidth);
  tempTensors.mergeCandidatesAddend.resize(beamwidth);

  const std::vector<size_t> mergeTensorsShape = {batchSize, beamwidth, 1};
  for (unsigned i = 0; i < beamwidth; i++) {
    const auto debugStr = std::to_string(i);
    tempTensors.mergeCandidatesPb[i] = graph.addVariable(
        partialsType, mergeTensorsShape, {di, "mergeCandidatesPb_" + debugStr});
    tempTensors.mergeCandidatesPnb[i] =
        graph.addVariable(partialsType, mergeTensorsShape,
                          {di, "mergeCandidatesPnb_" + debugStr});
    tempTensors.mergeCandidatesPTotal[i] =
        graph.addVariable(partialsType, mergeTensorsShape,
                          {di, "mergeCandidatesPTotal_" + debugStr});

    tempTensors.mergeCandidatesParent[i] =
        graph.addVariable(UNSIGNED_INT, mergeTensorsShape,
                          {di, "mergeCandidatesParent_" + debugStr});
    tempTensors.mergeCandidatesAddend[i] =
        graph.addVariable(UNSIGNED_INT, mergeTensorsShape,
                          {di, "mergeCandidatesAddend_" + debugStr});

    mapAccordingToPlan(graph, tempTensors.mergeCandidatesPb[i],
                       PartitionType::MERGE, plan.parallel.merge, plan);
    mapAccordingToPlan(graph, tempTensors.mergeCandidatesPnb[i],
                       PartitionType::MERGE, plan.parallel.merge, plan);
    mapAccordingToPlan(graph, tempTensors.mergeCandidatesPTotal[i],
                       PartitionType::MERGE, plan.parallel.merge, plan);
    mapAccordingToPlan(graph, tempTensors.mergeCandidatesParent[i],
                       PartitionType::MERGE, plan.parallel.merge, plan);
    mapAccordingToPlan(graph, tempTensors.mergeCandidatesAddend[i],
                       PartitionType::MERGE, plan.parallel.merge, plan);
  }
  return tempTensors;
}

// Make a vector of sort tensors, one for each sorting stage.
// When sorting using the `RANK` method, each stage has an input, ranked result,
// and output.  When sorting using the `SIMPLE_SORT` method each stage has an
// input and output only.
// The first stage input is the combined extend and copy candidates,
// the last stage output is the copy candidates.

std::vector<SortTensors>
createSortTensors(Graph &graph, const TempTensors &tempTensors,
                  const popnn::ctc::InferencePlan &plan, unsigned numClassesM1,
                  unsigned batchSize, unsigned beamwidth,
                  const Type &partialsType, Sequence &prog,
                  const poplar::DebugContext &di) {

  const unsigned numStages = plan.parallel.sort.size();
  std::vector<SortTensors> sortTensors(numStages);

  // Sort input for the first stage is the copy candidates followed by all
  // extend candidates.
  auto gatherCandidates = [=](const Tensor &copyIn, const Tensor &extendIn) {
    return concat(copyIn.reshape({batchSize, beamwidth}),
                  extendIn.reshape({batchSize, numClassesM1 * beamwidth}), 1);
  };
  sortTensors[0].inCandidatesParent = gatherCandidates(
      tempTensors.copyCandidatesParent, tempTensors.extendCandidatesParent);
  sortTensors[0].inCandidatesPnb = gatherCandidates(
      tempTensors.copyCandidatesPnb, tempTensors.extendCandidatesPnb);
  sortTensors[0].inCandidatesPb = gatherCandidates(
      tempTensors.copyCandidatesPb, tempTensors.extendCandidatesPb);
  // The addend and total probability are modified by the select extend vertex
  // where merges are detected.  Use that modified tensor.
  sortTensors[0].inCandidatesAddend =
      gatherCandidates(tempTensors.copyCandidatesAddend,
                       tempTensors.selectExtendCandidatesAddend);
  sortTensors[0].inCandidatesPTotal =
      gatherCandidates(tempTensors.copyCandidatesPTotal,
                       tempTensors.selectExtendCandidatesPTotal);

  for (unsigned stage = 0; stage < numStages; stage++) {
    const auto stageStr = "_" + std::to_string(stage);
    const auto groups = plan.parallel.sort[stage].groups;

    if (stage != 0) {
      // For all but the first stage, reference the previous stage output
      // tensors as the input
      sortTensors[stage].inCandidatesPb =
          sortTensors[stage - 1].outCandidatesPb;
      sortTensors[stage].inCandidatesPnb =
          sortTensors[stage - 1].outCandidatesPnb;
      sortTensors[stage].inCandidatesPTotal =
          sortTensors[stage - 1].outCandidatesPTotal;
      sortTensors[stage].inCandidatesParent =
          sortTensors[stage - 1].outCandidatesParent;
      sortTensors[stage].inCandidatesAddend =
          sortTensors[stage - 1].outCandidatesAddend;
    }

    // Create the intermediate results needed by the sort method for this stage
    const auto numRankedOutputs = plan.parallel.sort[stage].rankPartitions;
    const std::vector<std::size_t> rankedCandidateShape = {
        batchSize, numRankedOutputs * plan.parallel.sort[stage].groups,
        beamwidth};

    sortTensors[stage].rankedCandidatesPb = graph.addVariable(
        FLOAT, rankedCandidateShape, {di, "rankedCandidatesPb" + stageStr});
    sortTensors[stage].rankedCandidatesPnb = graph.addVariable(
        FLOAT, rankedCandidateShape, {di, "rankedCandidatesPnb" + stageStr});
    sortTensors[stage].rankedCandidatesPTotal = graph.addVariable(
        FLOAT, rankedCandidateShape, {di, "rankedCandidatesTotal" + stageStr});

    sortTensors[stage].rankedCandidatesParent =
        graph.addVariable(UNSIGNED_INT, rankedCandidateShape,
                          {di, "rankedCandidatesParent" + stageStr});
    sortTensors[stage].rankedCandidatesAddend =
        graph.addVariable(UNSIGNED_INT, rankedCandidateShape,
                          {di, "rankedCandidatesAddend" + stageStr});
    mapSortAccordingToPlan(graph, sortTensors[stage].rankedCandidatesPb,
                           PartitionType::SORT, stage, groups, numRankedOutputs,
                           plan);
    mapSortAccordingToPlan(graph, sortTensors[stage].rankedCandidatesPnb,
                           PartitionType::SORT, stage, groups, numRankedOutputs,
                           plan);
    mapSortAccordingToPlan(graph, sortTensors[stage].rankedCandidatesPTotal,
                           PartitionType::SORT, stage, groups, numRankedOutputs,
                           plan);
    mapSortAccordingToPlan(graph, sortTensors[stage].rankedCandidatesParent,
                           PartitionType::SORT, stage, groups, numRankedOutputs,
                           plan);
    mapSortAccordingToPlan(graph, sortTensors[stage].rankedCandidatesAddend,
                           PartitionType::SORT, stage, groups, numRankedOutputs,
                           plan);

    if (stage != numStages - 1) {
      // For all but the last stage, create output tensors
      const std::vector<std::size_t> shape = {batchSize, groups * beamwidth, 1};

      sortTensors[stage].outCandidatesPb = graph.addVariable(
          FLOAT, shape, {di, "sortedCandidatesPb" + stageStr});
      sortTensors[stage].outCandidatesPnb = graph.addVariable(
          FLOAT, shape, {di, "sortedCandidatesPnb" + stageStr});
      sortTensors[stage].outCandidatesPTotal = graph.addVariable(
          FLOAT, shape, {di, "sortedCandidatesPTotal" + stageStr});

      sortTensors[stage].outCandidatesParent = graph.addVariable(
          UNSIGNED_INT, shape, {di, "sortedCandidatesParent" + stageStr});
      sortTensors[stage].outCandidatesAddend = graph.addVariable(
          UNSIGNED_INT, shape, {di, "sortedCandidatesAddend" + stageStr});

      const auto numStageOutputs = plan.parallel.sort[stage].reducePartitions;

      mapSortAccordingToPlan(graph, sortTensors[stage].outCandidatesPb,
                             PartitionType::SORT_REDUCE, stage, groups,
                             numStageOutputs, plan);
      mapSortAccordingToPlan(graph, sortTensors[stage].outCandidatesPnb,
                             PartitionType::SORT_REDUCE, stage, groups,
                             numStageOutputs, plan);
      mapSortAccordingToPlan(graph, sortTensors[stage].outCandidatesPTotal,
                             PartitionType::SORT_REDUCE, stage, groups,
                             numStageOutputs, plan);
      mapSortAccordingToPlan(graph, sortTensors[stage].outCandidatesParent,
                             PartitionType::SORT_REDUCE, stage, groups,
                             numStageOutputs, plan);
      mapSortAccordingToPlan(graph, sortTensors[stage].outCandidatesAddend,
                             PartitionType::SORT_REDUCE, stage, groups,
                             numStageOutputs, plan);
    }
  }
  // Sorted result for the last stage is in the original copy candidates
  sortTensors.back().outCandidatesParent = tempTensors.copyCandidatesParent;
  sortTensors.back().outCandidatesAddend = tempTensors.copyCandidatesAddend;
  sortTensors.back().outCandidatesPnb = tempTensors.copyCandidatesPnb;
  sortTensors.back().outCandidatesPb = tempTensors.copyCandidatesPb;
  sortTensors.back().outCandidatesPTotal = tempTensors.copyCandidatesPTotal;

  return sortTensors;
}

BeamTensors createAndInitialiseBeamTensors(
    Graph &graph, const popnn::ctc::InferencePlan &plan, unsigned batchSize,
    unsigned maxT, unsigned beamwidth, const Type &partialsType, Sequence &prog,
    const poplar::DebugContext &di) {
  BeamTensors beamTensors;

  // Include an additional time step so that we can make a helpful initial
  // state
  const std::vector<std::size_t> beamHistoryShape = {
      batchSize, plan.batchEntryPartitions(), maxT + 1, beamwidth};
  beamTensors.parent =
      graph.addVariable(UNSIGNED_INT, beamHistoryShape, {di, "beamParent"});
  beamTensors.addend =
      graph.addVariable(UNSIGNED_INT, beamHistoryShape, {di, "beamAddend"});

  const std::vector<std::size_t> beamProbsShape = {
      batchSize, plan.batchEntryPartitions(), beamwidth, 1};
  beamTensors.pb =
      graph.addVariable(partialsType, beamProbsShape, {di, "beamPb"});
  beamTensors.pnb =
      graph.addVariable(partialsType, beamProbsShape, {di, "beamPnb"});
  beamTensors.pTotal =
      graph.addVariable(partialsType, beamProbsShape, {di, "beamPTotal"});
  beamTensors.lastOutput =
      graph.addVariable(UNSIGNED_INT, beamProbsShape, {di, "beamLastOutput"});

  mapAccordingToPlan(graph, beamTensors.parent, PartitionType::BATCH_ENTRY,
                     plan);
  mapAccordingToPlan(graph, beamTensors.addend, PartitionType::BATCH_ENTRY,
                     plan);

  mapAccordingToPlan(graph, beamTensors.pb, PartitionType::BATCH_ENTRY, plan);
  mapAccordingToPlan(graph, beamTensors.pnb, PartitionType::BATCH_ENTRY, plan);
  mapAccordingToPlan(graph, beamTensors.pTotal, PartitionType::BATCH_ENTRY,
                     plan);
  mapAccordingToPlan(graph, beamTensors.lastOutput, PartitionType::BATCH_ENTRY,
                     plan);

  const std::vector<std::size_t> beamLengthShape = {
      batchSize, plan.batchEntryPartitions(), 2 * beamwidth, 1};
  beamTensors.length =
      graph.addVariable(UNSIGNED_INT, beamLengthShape, {di, "beamLength"});
  mapAccordingToPlan(graph, beamTensors.length, PartitionType::BATCH_ENTRY,
                     plan);

  // Initialise the beam probabilities, with only one origin point
  auto initialiserProbZero =
      graph.addConstant<float>(partialsType, {1}, log::probabilityZero, di);
  graph.setTileMapping(initialiserProbZero, 0);
  auto initialiserProbOne =
      graph.addConstant<float>(partialsType, {1}, log::probabilityOne, di);
  graph.setTileMapping(initialiserProbOne, 0);

  auto initialiseBeamProbZero = [&](const Tensor &t) {
    auto tSliceOne = t.slice(0, 1, 2);
    auto tSliceZero = t.slice(1, beamwidth, 2);
    prog.add(Copy(initialiserProbOne.broadcast(tSliceOne.numElements(), 0),
                  tSliceOne.flatten(), false, di));
    prog.add(Copy(initialiserProbZero.broadcast(tSliceZero.numElements(), 0),
                  tSliceZero.flatten(), false, di));
  };
  initialiseBeamProbZero(beamTensors.pnb);
  initialiseBeamProbZero(beamTensors.pTotal);

  // Zero symbols per beam to start with
  auto initialiserZero = graph.addConstant(UNSIGNED_INT, {1}, 0u, di);
  graph.setTileMapping(initialiserZero, 0);
  prog.add(Copy(initialiserZero.broadcast(beamTensors.length.numElements(), 0),
                beamTensors.length.flatten(), false, di));

  // Setup the initial beam history with a zero time slice with:
  // beam   parent addend
  // 0      0      voidSymbol      (This is valid, but void)
  // 1      1      invalidSymbol   (These are invalid symbols and won't merge)
  // 2      1      invalidSymbol
  // 3      1      invalidSymbol
  // ...
  auto initialiserVoidSymbol =
      graph.addConstant(UNSIGNED_INT, {1}, voidSymbol, di);
  graph.setTileMapping(initialiserVoidSymbol, 0);
  auto initialiserInvalidSymbol =
      graph.addConstant(UNSIGNED_INT, {1}, invalidSymbol, di);
  graph.setTileMapping(initialiserInvalidSymbol, 0);

  auto initialiseTimestep0 = [&](const Tensor &t, const Tensor &init0,
                                 const Tensor &initOthers) {
    auto tSliceBeam0 = t.slice(0, 1, 2).slice(0, 1, 3);
    auto tSliceOtherBeams = t.slice(0, 1, 2).slice(1, beamwidth, 3);
    prog.add(Copy(init0.broadcast(tSliceBeam0.numElements(), 0),
                  tSliceBeam0.flatten()));
    prog.add(Copy(initOthers.broadcast(tSliceOtherBeams.numElements(), 0),
                  tSliceOtherBeams.flatten()));
  };

  auto initialiserOne = graph.addConstant(UNSIGNED_INT, {1}, 1u, di);
  graph.setTileMapping(initialiserOne, 0);
  initialiseTimestep0(beamTensors.parent, initialiserZero, initialiserOne);
  initialiseTimestep0(beamTensors.addend, initialiserVoidSymbol,
                      initialiserInvalidSymbol);

  // Last symbol[beam0] = voidSymbol initial state, others = invalidSymbol.
  // Although this is the same as the addend at the end of the beam history and
  // could be optimised out it doesn't cost much - it's stored on each tile,
  // never exchanged and updating it is the same speed path as other update
  // paths in the update vertex.  It speeds up candidate generation marginally
  // by avoiding indexing into the beam history
  const auto lastOutBeam0 = beamTensors.lastOutput.slice(0, 1, 2);
  const auto lastOutOtherBeams = beamTensors.lastOutput.slice(1, beamwidth, 2);

  prog.add(Copy(initialiserVoidSymbol.broadcast(lastOutBeam0.numElements(), 0),
                lastOutBeam0.flatten(), false, di));
  prog.add(Copy(
      initialiserInvalidSymbol.broadcast(lastOutOtherBeams.numElements(), 0),
      lastOutOtherBeams.flatten(), false, di));

  return beamTensors;
}
// A class to manage incrementing a variable and providing the partition that
// variable's value is in.  For example:
// batchSize =  5, 3 partitions with size 2, 2, 1
// [partitionIdx = 0,idx = 0], [0,1], [1,2], [1,3], [2,4]
struct PartitionIdx {
  unsigned partitionIdx;
  unsigned idx;
};
bool operator!=(PartitionIdx a, PartitionIdx b) { return a.idx != b.idx; }

class PartitionCounter {
  Interval partition;
  const popnn::ctc::InferencePlan &plan;
  const unsigned size;
  boost::variant<PartitionFn, SortPartitionFn> partitionFunction;
  PartitionIdx indices;
  boost::optional<unsigned> stage;

  Interval applyPartitionFunction(void) {
    return boost::apply_visitor(
        poplibs_support::make_visitor<Interval>(
            [=](const PartitionFn &fn) {
              return (plan.*fn)(size, indices.partitionIdx);
            },
            [=](const SortPartitionFn &fn) {
              return (plan.*fn)(size, indices.partitionIdx, *stage);
            }),
        partitionFunction);
  }

public:
  PartitionCounter(const popnn::ctc::InferencePlan &inPlan, unsigned inSize,
                   PartitionType partitionType)
      : plan(inPlan), size(inSize) {
    indices.partitionIdx = 0;
    indices.idx = 0;
    partitionFunction = PartitionFn(getPartitionFunction(partitionType));

    partition = applyPartitionFunction();
  };

  PartitionCounter(const popnn::ctc::InferencePlan &inPlan, unsigned inSize,
                   PartitionType partitionType, unsigned inStage)
      : plan(inPlan), size(inSize), stage(inStage) {
    indices.partitionIdx = 0;
    indices.idx = 0;
    partitionFunction =
        SortPartitionFn(getSortPartitionFunction(partitionType));
    partition = applyPartitionFunction();
  };

  PartitionIdx next(void) {
    indices.idx++;
    if (indices.idx == partition.end()) {
      indices.partitionIdx++;
      partition = applyPartitionFunction();
    }
    return indices;
  }

  PartitionIdx begin(void) {
    indices.partitionIdx = 0;
    indices.idx = 0;
    partition = applyPartitionFunction();
    return indices;
  }

  PartitionIdx end(void) { return PartitionIdx{size, size}; }
};

std::vector<ComputeSet>
sortCandidates(Graph &graph, const popnn::ctc::InferencePlan &plan,
               const TempTensors &tempTensors, const SortTensors &sortTensors,
               unsigned stage, unsigned beamwidth, unsigned batchSize,
               unsigned numClassesM1, const poplar::DebugContext &di) {

  std::vector<ComputeSet> css;

  // There are 2 methods: SIMPLE_SORT and RANK which can have benefits depending
  // on the number of workers available
  const auto stageGroups = plan.parallel.sort[stage].groups;
  const unsigned candidatesToCompare = sortTensors.inCandidatesPb.dim(1);
  const unsigned perGroup = ceildiv(candidatesToCompare, stageGroups);

  logging::popnn::debug(
      "CTCInference Sort stage {}, sorting {} candidates in {} group(s) of {}"
      " resulting in {} candidates",
      stage, candidatesToCompare, stageGroups, perGroup,
      stageGroups * beamwidth);

  // Rank step 1 -  rank each candidate, writing into the partition's
  // result if in the top beamwidth rankings
  css.push_back(graph.addComputeSet(di));
  PartitionCounter batch(plan, batchSize, PartitionType::BATCH);
  for (auto b = batch.begin(); b != batch.end(); b = batch.next()) {
    for (unsigned group = 0; group < stageGroups; group++) {

      const auto groupStart = plan.parallel.sort[stage].rankPartitions * group;
      // The range of candidates belonging to this group
      const Interval groupRange = {
          perGroup * group,
          std::min(perGroup * (group + 1), candidatesToCompare)};

      // Ranking the candidates in the group is split into partitions:
      for (unsigned ranking = 0;
           ranking < plan.parallel.sort[stage].rankPartitions; ranking++) {
        const unsigned tile =
            plan.getTile(b.partitionIdx, 0, stage, group, ranking);
        const auto perPartition =
            ceildiv(static_cast<unsigned>(groupRange.size()),
                    plan.parallel.sort[stage].rankPartitions);

        const auto firstToRank = perPartition * ranking + groupRange.begin();
        if (firstToRank >= candidatesToCompare) {
          break;
        }
        const Interval rangeToRank = {
            firstToRank,
            std::min(firstToRank + perPartition, groupRange.end())};

        const auto beamPartition =
            group / plan.parallel.sort[stage].groupsPerTile;

        rankCandidatesVertex(graph, tempTensors, sortTensors, css.back(), b.idx,
                             groupStart + ranking, beamPartition, groupRange,
                             rangeToRank, beamwidth, tile);
      }
    }
  }
  // Rank step 2 - reduce all the partition's results down to 1 result
  // (beamwidth values) per batch entry
  css.push_back(graph.addComputeSet(di));
  for (auto b = batch.begin(); b != batch.end(); b = batch.next()) {
    for (unsigned group = 0; group < stageGroups; group++) {

      const auto groupStart = group * beamwidth;
      // The group of elements to reduce in this stage comes from
      // the ranking output
      const auto firstToReduce =
          group * plan.parallel.sort[stage].rankPartitions;
      const Interval toReduce = {firstToReduce,
                                 firstToReduce +
                                     plan.parallel.sort[stage].rankPartitions};

      // Reducing the ranked results is split into partitions:
      PartitionCounter reduce(plan, beamwidth, PartitionType::SORT_REDUCE,
                              stage);
      for (auto r = reduce.begin(); r != reduce.end(); r = reduce.next()) {
        const auto tile =
            plan.getTile(b.partitionIdx, 0, stage, group, r.partitionIdx);

        const auto beamPartition =
            group / plan.parallel.sort[stage].groupsPerTile;

        reduceCandidatesVertex(graph, tempTensors, sortTensors, css.back(),
                               b.idx, groupStart + r.idx, r.idx, beamPartition,
                               toReduce, tile);
      }
    }
  }

  return css;
}

Sequence createLoopBodyProg(Graph &graph, const popnn::ctc::InferencePlan &plan,
                            const Tensor &data, const BeamTensors &beams,
                            const TempTensors &tempTensors,
                            std::vector<SortTensors> &sortTensors,
                            unsigned blankClass, unsigned beamwidth,
                            const poplar::DebugContext &di) {

  const unsigned batchSize = data.dim(0);
  const auto maxT = data.dim(2);
  const auto numClasses = data.dim(3);
  const auto numClassesM1 = numClasses - 1;
  Sequence prog;

  prog.add(Copy(tempTensors.loopTimestep.flatten().broadcast(
                    tempTensors.currentTimestep.numElements(), 0),
                tempTensors.currentTimestep.flatten()));

  // Generate candidates in the 1st compute set
  auto cs1 = graph.addComputeSet(di);
  PartitionCounter batch(plan, batchSize, PartitionType::BATCH);
  for (auto b = batch.begin(); b != batch.end(); b = batch.next()) {
    // Extend candidates
    PartitionCounter extend(plan, numClassesM1, PartitionType::EXTEND);
    for (auto e = extend.begin(); e != extend.end(); e = extend.next()) {
      const unsigned tile = plan.getTile(b.partitionIdx, 0, e.partitionIdx);
      const unsigned addendClass = e.idx >= blankClass ? e.idx + 1 : e.idx;
      for (unsigned v = 0; v < plan.parallel.extendVerticesPerPartition; v++) {
        const auto beamPartition = plan.partitionExtendVertices(beamwidth, v);
        generateExtendCandidateVertex(graph, data, beams, tempTensors, cs1,
                                      b.idx, {0, maxT}, e.idx, e.partitionIdx,
                                      blankClass, beamwidth, beamPartition,
                                      addendClass, tile);
      }
    }
    // Copy candidates
    PartitionCounter copy(plan, beamwidth, PartitionType::COPY);
    for (auto c = copy.begin(); c != copy.end(); c = copy.next()) {
      const unsigned tile = plan.getTile(b.partitionIdx, 0, c.partitionIdx);
      generateCopyCandidateVertex(graph, data, beams, tempTensors, cs1, b.idx,
                                  {0, maxT}, c.idx, c.partitionIdx, blankClass,
                                  beamwidth, tile);
    }
  }
  prog.add(Execute(cs1, di));

  // Broadcast the copy candidates into the vectors of merge candidates
  auto transform = [=](const Tensor &in, unsigned i) {
    return in.slice(i, i + 1, 1).expand({1}).broadcast(beamwidth, 1);
  };

  for (unsigned i = 0; i < beamwidth; i++) {
    prog.add(Copy(transform(tempTensors.copyCandidatesParent, i),
                  tempTensors.mergeCandidatesParent[i]));
    prog.add(Copy(transform(tempTensors.copyCandidatesAddend, i),
                  tempTensors.mergeCandidatesAddend[i]));
    prog.add(Copy(transform(tempTensors.copyCandidatesPb, i),
                  tempTensors.mergeCandidatesPb[i]));
    prog.add(Copy(transform(tempTensors.copyCandidatesPnb, i),
                  tempTensors.mergeCandidatesPnb[i]));
    prog.add(Copy(transform(tempTensors.copyCandidatesPTotal, i),
                  tempTensors.mergeCandidatesPTotal[i]));
  }
  // Copy to provide the extend candidate total probability and addend on the
  // correct tiles for the Select stage.  This data isn't changed in the Merge
  // stage, so we can do the copies all together to allow poplar to optimise
  // the operation.
  prog.add(Copy(tempTensors.extendCandidatesPTotal,
                tempTensors.selectExtendCandidatesPTotal));
  prog.add(Copy(tempTensors.extendCandidatesAddend,
                tempTensors.selectExtendCandidatesAddend));

  // Merge candidates in the 2nd compute set
  auto cs2 = graph.addComputeSet(di);
  for (auto b = batch.begin(); b != batch.end(); b = batch.next()) {
    for (unsigned copy = 0; copy < beamwidth; copy++) {
      PartitionCounter merge(plan, beamwidth, PartitionType::MERGE);
      for (auto m = merge.begin(); m != merge.end(); m = merge.next()) {
        const unsigned tile = plan.getTile(b.partitionIdx, 0, m.partitionIdx);
        mergeCandidateVertex(graph, beams, tempTensors, cs2, b.idx, {0, maxT},
                             m.idx, copy, m.partitionIdx, blankClass, beamwidth,
                             numClasses, tile);
      }
    }
  }
  prog.add(Execute(cs2, di));

  // Select the merged copy candidates, and zero the probability of merged
  // extend candidates in the 3rd compute set
  auto cs3 = graph.addComputeSet(di);
  for (auto b = batch.begin(); b != batch.end(); b = batch.next()) {
    PartitionCounter copy(plan, beamwidth, PartitionType::SELECT_COPY);
    for (auto c = copy.begin(); c != copy.end(); c = copy.next()) {
      const unsigned tile = plan.getTile(b.partitionIdx, 0, c.partitionIdx);
      selectCopyCandidateVertex(graph, tempTensors, cs3, b.idx, c.idx,
                                c.partitionIdx, beamwidth, tile);
    }
    PartitionCounter extend(plan, beamwidth, PartitionType::SELECT_EXTEND);
    for (auto e = extend.begin(); e != extend.end(); e = extend.next()) {
      const unsigned tile = plan.getTile(b.partitionIdx, 0, e.partitionIdx);
      selectExtendCandidateVertex(graph, tempTensors, cs3, b.idx, e.idx,
                                  e.partitionIdx, beamwidth, blankClass, tile);
    }
  }
  prog.add(Execute(cs3, di));

  // Sort candidates in the 4th compute set
  for (unsigned stage = 0; stage < plan.parallel.sort.size(); stage++) {
    auto sortCss =
        sortCandidates(graph, plan, tempTensors, sortTensors[stage], stage,
                       beamwidth, batchSize, numClassesM1, di);
    for (auto &cs : sortCss) {
      prog.add(Execute(cs, di));
    }
  }
  // Update beam history and probabilities in the 5th compute set
  auto cs5 = graph.addComputeSet(di);
  for (auto b = batch.begin(); b != batch.end(); b = batch.next()) {
    for (unsigned beam = 0; beam < plan.batchEntryPartitions(); beam++) {
      const unsigned tile = plan.getTile(b.partitionIdx, 0, beam);
      updateVertex(graph, beams, tempTensors, cs5, b.idx, {0, maxT}, beam,
                   beamwidth, tile);
    }
  }
  prog.add(Execute(cs5, di));

  return prog;
}

Tensor toInternalShape(const Tensor &data) {
  // We are supplied a data input Tensor with shape
  // [maxTime, batchSize, numClasses].
  // Internally, data is ordered differently, and we will broadcast this data
  // according to the number of partitions made. So internally we use:
  // [batchSize, batchEntryPartitions, maxTime,  numClasses]
  // Here we have not yet broadcast so batchEntryPartitions = 1
  return data.dimShufflePartial({0}, {1}).expand({1});
}

Tensor toExternalShape(const Tensor &data) {
  // Return to the external shape.
  return data.dimShufflePartial({0}, {1});
}

void validateTensorTypes(const poplar::Tensor &data,
                         const poplar::Tensor &dataLengths,
                         const poplar::Type &partialsType,
                         const poplar::Type &outType) {
  if (data.elementType() != poplar::HALF &&
      data.elementType() != poplar::FLOAT) {
    throw poputil::poplibs_error("data tensor must be of type HALF or FLOAT");
  }
  if (dataLengths.elementType() != poplar::UNSIGNED_INT) {
    throw poputil::poplibs_error(
        "dataLengths tensor must be of type UNSIGNED_INT");
  }
  if (partialsType == poplar::HALF && data.elementType() == poplar::FLOAT) {
    throw poputil::poplibs_error(
        "partials type HALF unsupported with input tensor type FLOAT");
  }
  if (outType != poplar::HALF && outType != poplar::FLOAT) {
    throw poputil::poplibs_error("outType must be of type HALF or FLOAT");
  }
}

} // namespace
namespace popnn {
namespace ctc_infer {

poplar::Tensor createDataInput(poplar::Graph &graph, const poplar::Type &type,
                               const std::size_t batchSize,
                               const std::size_t maxTime,
                               const std::size_t numClasses,
                               const ctc::Plan &plan,
                               const poplar::DebugContext &debugContext) {
  const auto &inferPlan = plan.getImpl().getAsInferencePlan();
  POPNN_TRACEPOINT();
  poputil::PoplibsOpDebugInfo di(
      debugContext, DI_ARGS(type, batchSize, maxTime, numClasses, plan));

  logging::popnn::debug("Creating data tensor for CTC beam search with Time:{}"
                        " Batches:{} Classes:{}",
                        maxTime, batchSize, numClasses);
  const auto data = graph.addVariable(type, {batchSize, 1, maxTime, numClasses},
                                      {di, "data"});
  mapDataInputAccordingToPlan(graph, data, inferPlan);
  di.addOutput(data);
  return toExternalShape(data.squeeze({1}));
}

// beamSearchDecoderLogProbabilitiesImpl output tuple:
// outType  Tensor  labelProbs[batchSize, topPaths]
// unsigned Tensor  labelLengths[batchSize, topPaths]
// unsigned Tensor  decodedLabels[batchSize, topPaths, maxTime]
std::tuple<poplar::Tensor, poplar::Tensor, poplar::Tensor>
beamSearchDecoderLogProbabilitiesImpl(
    poplar::Graph &graph, const poplar::Type &outType,
    const poplar::Tensor &data, const poplar::Tensor &dataLengths,
    poplar::program::Sequence &prog, const unsigned blankClass,
    const unsigned beamwidth, const unsigned topPaths,
    const ctc::InferencePlan &plan, const poplar::DebugContext &debugContext,
    const poplar::OptionFlags &options) {

  const auto partialsType = plan.params.partialsType;
  validateTensorTypes(data, dataLengths, partialsType, outType);
  POPNN_TRACEPOINT();
  poputil::PoplibsOpDebugInfo di({debugContext, "CTCBeamSearchDecoder"},
                                 DI_ARGS(outType, data, dataLengths, blankClass,
                                         beamwidth, topPaths, plan, options));

  logging::popnn::debug("Disabled NANOO for CTC beam search decoder operation");
  poplar::FloatingPointBehaviour clear{false, false, false, false,
                                       true}; // Mask out nanoo
  poplar::FloatingPointBehaviour set{false, false, false, false, false};
  auto fpCSRToRestore =
      poplar::getAndModifyFloatingPointBehaviour(graph, prog, clear, set, di);

  logging::popnn::debug("Creating CTC beam search decoder using\n{}", plan);
  const auto maxT = data.dim(0);
  const auto batchSize = data.dim(1);
  const auto numClasses = data.dim(2);

  // Reshape the input for internal use
  auto internalData = toInternalShape(data);
  auto internalDataShape = internalData.shape();
  internalDataShape[1] = plan.batchEntryPartitions();

  // Broadcast the data input and map according to the planned totalPartitions
  // which require a copy of the data while computing
  const auto workingData = [&]() {
    if (plan.batchEntryPartitions() != 1) {
      auto result = graph.addVariable(data.elementType(), internalDataShape,
                                      {di, "broadcastInput"});
      mapAccordingToPlan(graph, result, PartitionType::BATCH_ENTRY, plan);
      auto broadcastData =
          internalData.broadcast(plan.batchEntryPartitions(), 1);
      prog.add(Copy(broadcastData, result, false, di));
      return result;
    } else {
      // No broadcast/copy to do
      return internalData;
    }
  }();

  // Make the beam history tensors, setting only 1 beam to probablity = 1
  // and all outputs = voidSymbol
  auto beams = createAndInitialiseBeamTensors(
      graph, plan, batchSize, maxT, beamwidth, partialsType, prog, di);
  // Make the temporary tensors, initialising only the count
  auto tempTensors = createAndInitialiseTemporaryTensors(
      graph, dataLengths, plan, numClasses, batchSize, beamwidth, partialsType,
      prog, di);

  auto sortTensors =
      createSortTensors(graph, tempTensors, plan, numClasses - 1, batchSize,
                        beamwidth, partialsType, prog, di);

  // Make the loop body and run it
  auto loopBody = createLoopBodyProg(graph, plan, workingData, beams,
                                     tempTensors, sortTensors, blankClass,
                                     beamwidth, {di, "beamSearchDecoderLoop"});
  // The first timestep is 1, with timestep 0 being an initial state so start
  // counting from 1
  prog.add(countedForLoop(graph, tempTensors.loopTimestep, 1,
                          tempTensors.maxTimeInBatch, 1, loopBody, di));

  // Create results and map, inserting a 3rd dimension so mapping functions can
  // be used
  auto labelLengths = graph.addVariable(UNSIGNED_INT, {batchSize, topPaths},
                                        {di, "labelLengths"});
  auto decodedLabels = graph.addVariable(
      UNSIGNED_INT, {batchSize, topPaths, maxT}, {di, "decodedLabels"});

  mapAccordingToPlan(graph, labelLengths.expand({2}), PartitionType::OUTPUT,
                     plan.parallel.output, plan);
  mapAccordingToPlan(graph, decodedLabels, PartitionType::OUTPUT,
                     plan.parallel.output, plan);

  auto outputCS = graph.addComputeSet({di, "output"});
  PartitionCounter batch(plan, batchSize, PartitionType::BATCH);
  for (auto b = batch.begin(); b != batch.end(); b = batch.next()) {
    PartitionCounter output(plan, topPaths, PartitionType::OUTPUT);
    for (auto o = output.begin(); o != output.end(); o = output.next()) {
      const unsigned tile = plan.getTile(b.partitionIdx, 0, o.partitionIdx);
      generateOutputVertex(graph, beams, tempTensors, decodedLabels,
                           labelLengths, outputCS, b.idx, o.idx, o.partitionIdx,
                           beamwidth, numClasses, tile);
    }
  }
  prog.add(Execute(outputCS, di));
  // Slice the correct output and cast if needed
  auto transform = [&](const Tensor &in) {
    return in.slice(0, 1, 1)
        .slice(0, topPaths, 2)
        .reshape({batchSize, topPaths});
  };
  auto pTotal = transform(beams.pTotal);
  auto labelProbs = [&]() {
    if (partialsType != outType) {
      poplar::DebugContext castDebug{di, "Cast"};
      auto castCS = graph.addComputeSet(castDebug);
      auto probs = popops::cast(graph, pTotal, outType, castCS, castDebug);
      prog.add(Execute(castCS, castDebug));
      return probs;
    } else {
      return pTotal;
    };
  }();

  di.addOutputs({{"labelProbs", poputil::toProfileValue(labelProbs)},
                 {"labelLengths", poputil::toProfileValue(labelLengths)},
                 {"decodedLabels", poputil::toProfileValue(labelLengths)}});

  poplar::setFloatingPointBehaviour(graph, prog, fpCSRToRestore, di);
  return {labelProbs, labelLengths, decodedLabels};
}

void printOp(std::string name, const poplar::Type &partialsType,
             const poplar::Type &outType, const poplar::Tensor &data,
             const poplar::Tensor &dataLengths, const unsigned blankClass,
             const unsigned beamWidth, const unsigned topPaths,
             const poplar::DebugContext &debugContext) {
  const auto inType = data.elementType();
  logging::popnn::info("{} data={}, dataLengths={}, "
                       "blankClass={}, beamwidth={}, topPaths={}, inType={}, "
                       "partialsType={}, outType={}, name={}",
                       name, data.shape(), dataLengths.shape(), blankClass,
                       beamWidth, topPaths, inType, partialsType, outType,
                       debugContext.getPathName());
}
std::tuple<poplar::Tensor, poplar::Tensor, poplar::Tensor>
beamSearchDecoderLogProbabilities(poplar::Graph &graph,
                                  const poplar::Tensor &logProbs,
                                  const poplar::Tensor &dataLengths,
                                  poplar::program::Sequence &prog,
                                  unsigned blankClass, unsigned beamwidth,
                                  unsigned topPaths, const ctc::Plan &plan,
                                  const poplar::DebugContext &debugContext,
                                  const poplar::OptionFlags &options) {

  const auto &inferPlan = plan.getImpl().getAsInferencePlan();
  const auto partialsType = inferPlan.params.partialsType;
  const auto outType = inferPlan.params.outType;
  printOp("CTCBeamSearchDecoderLogProbs", partialsType, outType, logProbs,
          dataLengths, blankClass, beamwidth, topPaths, debugContext);

  return beamSearchDecoderLogProbabilitiesImpl(
      graph, outType, logProbs, dataLengths, prog, blankClass, beamwidth,
      topPaths, inferPlan, {debugContext, "CTCBeamSearchDecoderLogProbs"},
      options);
}

std::tuple<poplar::Tensor, poplar::Tensor, poplar::Tensor>
beamSearchDecoderLogits(poplar::Graph &graph, const poplar::Tensor &logits,
                        const poplar::Tensor &dataLengths,
                        poplar::program::Sequence &prog, unsigned blankClass,
                        unsigned beamwidth, unsigned topPaths,
                        const ctc::Plan &plan,
                        const poplar::DebugContext &parentDebugContext,
                        const poplar::OptionFlags &options) {

  const auto &inferPlan = plan.getImpl().getAsInferencePlan();
  const auto partialsType = inferPlan.params.partialsType;
  const auto outType = inferPlan.params.outType;
  printOp("CTCBeamSearchDecoderLogits", partialsType, outType, logits,
          dataLengths, blankClass, beamwidth, topPaths, parentDebugContext);
  poplar::DebugContext debugContext{parentDebugContext,
                                    "CTCBeamSearchDecoderLogits"};

  // Ensure we preserve mapping of the result to fit in with the plan
  auto logProbs = graph.clone(logits, debugContext);
  prog.add(Copy(logits, logProbs, false, debugContext));
  logSoftmaxInPlace(graph, logProbs, prog, debugContext);

  return beamSearchDecoderLogProbabilitiesImpl(
      graph, outType, logProbs, dataLengths, prog, blankClass, beamwidth,
      topPaths, inferPlan, debugContext, options);
}

} // namespace ctc_infer

} // end namespace popnn

max_stars_repo_name: graphcore/poplibs

max_issues_repo_name: graphcore/poplibs

max_forks_repo_name: graphcore/poplibs


text:
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <boost/bind.hpp>
#include <boost/filesystem/operations.hpp>

#include "core/id.hpp"
#include "core/log.hpp"
#include "core/network/network_message.hpp"
#include "core/network/plain_socket.hpp"
#include "core/web/web_types.hpp"
#include "web/web_server.hpp"

using boost::asio::ip::tcp;

WebServer::WebServer(const Config &config)
    : mConfig(config)
    , mContext(config.workers)
    , mStrand(mContext)
    , mTCPEndpoint(tcp::v4(), config.port)
    , mTCPAcceptor(mContext, mTCPEndpoint)
    , mWebEndpoint(tcp::v4(), config.webPort)
    , mWebAcceptor(mContext, mWebEndpoint)
{
    tcpAccept();
    webAccept();
}

void WebServer::run() {
    // Check data directory
    if (!boost::filesystem::exists(mConfig.dataDirectory)) {
        if (!boost::filesystem::create_directory(mConfig.dataDirectory)) {
            log_error().field("path", mConfig.dataDirectory).msg("Failed creating data directory");
            return;
        }
    }
    else if (!boost::filesystem::is_directory(mConfig.dataDirectory)) {
        log_error().field("path", mConfig.dataDirectory).msg("The given data directory is not a directory");
        return;
    }

    // Launch database
    mDatabase = std::make_unique<Database>(mContext, mConfig.postgres);

    // Launch worker threads
    log_info().field("threadCount", mConfig.workers).msg("Launching worker threads");
    for (size_t i = 0; i < mConfig.workers; ++i) {
        mThreads.emplace_back(&WebServer::work, this);
    }

    // log_info().msg("Waiting for clients");
    // mDatabase->asyncRegisterUser("svendcsvendsen@gmail.com", "password", [this](UserRegistrationResponse response, const WebToken &token) {});

    work();

    for (std::thread &thread : mThreads)
        thread.join();
}

void WebServer::saveTournaments() {
    if (!mParticipants.empty()) // Wait for all tcp participants to close
        return;

    if (!mWebParticipants.empty()) // Wait for all web participants to close
        return;

    // Save and close all tournaments
    for (auto & p : mLoadedTournaments)
        p.second->saveIfNeccesary();
}

void WebServer::closeWebParticipants() {
    if (!mParticipants.empty()) // Wait for all tcp participants to close
        return;

    // Close all web participants
    if (!mWebParticipants.empty()) {
        for (auto & participant: mWebParticipants)
            participant->asyncClose(boost::asio::bind_executor(mStrand, boost::bind(&WebServer::saveTournaments, this)));
    }
    else {
        boost::asio::dispatch(mStrand, boost::bind(&WebServer::saveTournaments, this));
    }
}

void WebServer::quit() {
    boost::asio::post(mStrand, [this]() {
        mTCPAcceptor.close();
        mWebAcceptor.close();

        // Close all TCP participants
        if (!mParticipants.empty()) {
            for (auto & participant: mParticipants)
                participant->asyncClose(boost::asio::bind_executor(mStrand, boost::bind(&WebServer::closeWebParticipants, this)));
        }
        else {
            boost::asio::dispatch(mStrand, boost::bind(&WebServer::closeWebParticipants, this));
        }
    });
}

void WebServer::tcpAccept() {
    mTCPAcceptor.async_accept([this](boost::system::error_code ec, tcp::socket socket) {
        if (ec) {
            if (ec.value() != boost::system::errc::operation_canceled && ec.value() != boost::system::errc::bad_file_descriptor)
                log_error().field("message", ec.message()).msg("Received error code in tcp async_accept");
        }
        else {
            auto connection = std::make_shared<NetworkConnection>(std::make_unique<PlainSocket>(mContext, std::move(socket)));

            connection->asyncAccept(boost::asio::bind_executor(mStrand, [this, connection](boost::system::error_code ec) {
                if (ec)
                    return;

                log_info().msg("TCP Participant Joined");

                auto participant = std::make_shared<TCPParticipant>(mContext, std::move(connection), *this, *mDatabase);
                participant->asyncAuth();
                mParticipants.insert(std::move(participant));
            }));
        }

        if (mTCPAcceptor.is_open())
            tcpAccept();
    });
}

void WebServer::webAccept() {
    mWebAcceptor.async_accept([this](boost::system::error_code ec, tcp::socket socket) {
        if (ec) {
            if (ec.value() != boost::system::errc::operation_canceled && ec.value() != boost::system::errc::bad_file_descriptor)
                log_error().field("value", ec.value()).field("message", ec.message()).msg("Received error code in web async_accept");
        }
        else {
            auto connection = std::make_shared<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>(std::move(socket));

            connection->async_accept(boost::asio::bind_executor(mStrand, [this, connection](boost::beast::error_code ec) {
                if (ec) {
                    if (ec.value() != boost::system::errc::operation_canceled && ec.value() != boost::system::errc::bad_file_descriptor)
                        log_error().field("message", ec.message()).msg("Received error code in websocket async accept");
                    return;
                }

                log_info().msg("Web Participant Joined");

                auto participant = std::make_shared<WebParticipant>(mContext, std::move(connection), *this, *mDatabase);
                participant->listen();
                mWebParticipants.insert(std::move(participant));
            }));
        }

        if (mWebAcceptor.is_open())
            webAccept();
    });
}

void WebServer::leave(std::shared_ptr<TCPParticipant> participant, LeaveCallback callback) {
    boost::asio::post(mStrand, [this, participant, callback]() {
        log_info().msg("TCP Participant Left");
        mParticipants.erase(participant);
        boost::asio::post(mContext, callback);
    });
}

void WebServer::leave(std::shared_ptr<WebParticipant> participant, LeaveCallback callback) {
    boost::asio::post(mStrand, [this, participant, callback]() {
        log_info().msg("Web Participant Left");
        mWebParticipants.erase(participant);
        boost::asio::post(mContext, callback);
    });
}

void WebServer::assignWebName(std::shared_ptr<TCPParticipant> participant, std::string webName) {
    log_debug().field("webName", webName).msg("Assigning web name to participant");
}

void WebServer::work() {
    mContext.run();
}

void WebServer::acquireTournament(const std::string &webName, AcquireTournamentCallback callback) {
    boost::asio::dispatch(mStrand, [this, webName, callback]() {
        auto it = mLoadedTournaments.find(webName);
        std::shared_ptr<LoadedTournament> tournament;
        if (it != mLoadedTournaments.end()) {
            // TODO: Kick existing participant if any
            tournament = it->second;
        }
        else {
            tournament = std::make_shared<LoadedTournament>(webName, mConfig.dataDirectory, mContext, *mDatabase);
            mLoadedTournaments.insert({webName, tournament});
        }

        boost::asio::dispatch(mContext, std::bind(callback, std::move(tournament)));
    });
}

void WebServer::getTournament(const std::string &webName, GetTournamentCallback callback) {
    boost::asio::dispatch(mStrand, [this, webName, callback]() {
        auto it = mLoadedTournaments.find(webName);
        if (it != mLoadedTournaments.end()) {
            boost::asio::dispatch(mContext, std::bind(callback, it->second));
            return;
        }

        mDatabase->asyncGetSaveStatus(webName, boost::asio::bind_executor(mStrand, [this, webName, callback](bool isSaved) {
            if (!isSaved) {
                boost::asio::dispatch(mContext, std::bind(callback, nullptr));
                return;
            }

            auto tournament = std::make_shared<LoadedTournament>(webName, mConfig.dataDirectory, mContext, *mDatabase);
            tournament->load(boost::asio::bind_executor(mStrand, [this, webName, tournament, callback](bool success) {
                auto it = mLoadedTournaments.find(webName);
                if (it != mLoadedTournaments.end()) { // The tournament was loaded by someone else
                    boost::asio::dispatch(mContext, std::bind(callback, it->second));
                    return;
                }

                if (success) {
                    mLoadedTournaments.insert({webName, tournament});
                    boost::asio::dispatch(mContext, std::bind(callback, tournament));
                    return;
                }

                boost::asio::dispatch(mContext, std::bind(callback, nullptr));
                return;
            }));
        }));

    });
}

ext: cpp

max_stars_repo_name: svendcsvendsen/judoassistant

max_issues_repo_name: svendcsvendsen/judoassistant

max_forks_repo_name: judoassistant/judoassistant


text:
//
// ssl/error.hpp
// ~~~~~~~~~~~~~
//
// Copyright (c) 2003-2017 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef BOOST_ASIO_SSL_ERROR_HPP
#define BOOST_ASIO_SSL_ERROR_HPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include <boost/asio/detail/config.hpp>
#include <boost/system/error_code.hpp>
#include <boost/asio/ssl/detail/openssl_types.hpp>

#include <boost/asio/detail/push_options.hpp>

namespace boost {
namespace asio {
namespace error {

enum ssl_errors
{
  // Error numbers are those produced by openssl.
};

extern BOOST_ASIO_DECL
const boost::system::error_category& get_ssl_category();

static const boost::system::error_category&
  ssl_category BOOST_ASIO_UNUSED_VARIABLE
  = boost::asio::error::get_ssl_category();

} // namespace error
namespace ssl {
namespace error {

enum stream_errors
{
#if defined(GENERATING_DOCUMENTATION)
  /// The underlying stream closed before the ssl stream gracefully shut down.
  stream_truncated
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L) && !defined(OPENSSL_IS_BORINGSSL)
  stream_truncated = ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ)
#else
  stream_truncated = 1
#endif
};

extern BOOST_ASIO_DECL
const boost::system::error_category& get_stream_category();

static const boost::system::error_category&
  stream_category BOOST_ASIO_UNUSED_VARIABLE
  = boost::asio::ssl::error::get_stream_category();

} // namespace error
} // namespace ssl
} // namespace asio
} // namespace boost

namespace boost {
namespace system {

template<> struct is_error_code_enum<boost::asio::error::ssl_errors>
{
  static const bool value = true;
};

template<> struct is_error_code_enum<boost::asio::ssl::error::stream_errors>
{
  static const bool value = true;
};

} // namespace system
} // namespace boost

namespace boost {
namespace asio {
namespace error {

inline boost::system::error_code make_error_code(ssl_errors e)
{
  return boost::system::error_code(
      static_cast<int>(e), get_ssl_category());
}

} // namespace error
namespace ssl {
namespace error {

inline boost::system::error_code make_error_code(stream_errors e)
{
  return boost::system::error_code(
      static_cast<int>(e), get_stream_category());
}

} // namespace error
} // namespace ssl
} // namespace asio
} // namespace boost

#include <boost/asio/detail/pop_options.hpp>

#if defined(BOOST_ASIO_HEADER_ONLY)
# include <boost/asio/ssl/impl/error.ipp>
#endif // defined(BOOST_ASIO_HEADER_ONLY)

#endif // BOOST_ASIO_SSL_ERROR_HPP

ext: hpp


text:
// -*- mode: c++ -*-
/*********************************************************************
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2014, JSK Lab
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/o2r other materials provided
 *     with the distribution.
 *   * Neither the name of the JSK Lab nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************/

#include "tablet_controller_panel.h"
#include <Eigen/Core>
#include <Eigen/Geometry>
namespace jsk_rviz_plugins
{
  TabletCmdVelArea::TabletCmdVelArea(QWidget* parent, ros::Publisher& pub_cmd_vel):
    QWidget(parent), mouse_x_(-1), mouse_y_(-1), pub_cmd_vel_(pub_cmd_vel)
  {
    setBackgroundRole(QPalette::Base);
    setAutoFillBackground(true);
  }

  QSize TabletCmdVelArea::minimumSizeHint() const
  {
    return QSize(300, 300);
  }

  QSize TabletCmdVelArea::sizeHint() const
  {
    return QSize(300, 300);
  }

  void TabletCmdVelArea::mouseMoveEvent(QMouseEvent* event){
    mouse_x_ = event->x();
    mouse_y_ = event->y();
    repaint();
  }
  void TabletCmdVelArea::mousePressEvent(QMouseEvent* event){
    mouse_x_ = event->x();
    mouse_y_ = event->y();
    repaint();
  }
  void TabletCmdVelArea::mouseReleaseEvent(QMouseEvent* event){
    mouse_x_ = -1;
    mouse_y_ = -1;
    repaint();
    publishCmdVel(0, 0, 0);
  }

  void TabletCmdVelArea::paintEvent(QPaintEvent* event)
  {
    QSize widget_size = size();
    int line_width = 20;
    int width = widget_size.width() - line_width * 2;
    int height = widget_size.height() - line_width * 2;
    // out circle
    int radius = std::min(width, height) / 2;
    int center_x = width / 2 + line_width;
    int center_y = height / 2 + line_width;
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    QPen pen;
    pen.setColor(QColor(130, 177, 255));
    pen.setWidth(10);
    painter.setPen(pen);
    painter.drawArc(center_x - radius, center_y - radius,
                    radius * 2, radius * 2, 0, (360 + 1) * 16);
    // position of mouse
    QPen inner_pen;
    inner_pen.setColor(QColor(33, 150, 243));
    int inner_size = 40;
    inner_pen.setWidth(inner_size);
    painter.setPen(inner_pen);
    if (mouse_x_ == -1 && mouse_y_ == -1) {
      mouse_x_ = center_x;
      mouse_y_ = center_y;
    }
    else {
      publishVelocity(mouse_x_, mouse_y_, center_x, center_y);
    }
    painter.drawArc(mouse_x_ - inner_size / 2,
		    mouse_y_ - inner_size / 2,
		    inner_size, inner_size, 0, (360 + 1) * 16);
  }

  void TabletCmdVelArea::publishVelocity(
    int mouse_x, int mouse_y, int cx, int cy)
  {
    double diff_x = mouse_x - cx;
    double diff_y = mouse_y - cy;
    Eigen::Vector3d ex(0, -1, 0);
    Eigen::Vector3d vel(diff_x / cx, diff_y / cy, 0);

    int sign = 1;
    if (ex.cross(vel).dot(Eigen::Vector3d(0, 0, -1)) < 0) {
      sign = -1;
    }
    double dot = ex.dot(vel) / ex.norm() / vel.norm();
    if (dot < -1) {
      dot = -1.0;
    }
    else if (dot > 1) {
      dot = 1.0;
    }
    double theta = sign * acos(dot);
    if (!isnan(theta)) {
      Eigen::Vector3d vel_refined(-vel[1], -vel[0], 0);

      publishCmdVel(vel_refined[0] * 0.2 , vel_refined[1] * 0.2, theta * 0.2);
    }
  }

  void TabletCmdVelArea::publishCmdVel(double x, double y, double theta)
  {
    ROS_INFO("(%f, %f)", x, y);
    ROS_INFO("theta: %f", theta);
    geometry_msgs::Twist twist;
    twist.linear.x = x;
    twist.linear.y = y;
    twist.angular.z = theta;
    pub_cmd_vel_.publish(twist);
  }

  QString TabletControllerPanel::defaultButtonStyleSheet()
  {
    return "QPushButton {background-color: #FF5252; color: white; font-size: 30pt;}";
  }

  QString TabletControllerPanel::executeButtonStyleSheet()
  {
    return "QPushButton {background-color: white; color: #424242; font-size: 30pt;}";
  }

  QString TabletControllerPanel::radioButtonStyleSheet()
  {
    return "QRadioButton {font-size: 20pt; color: #424242;}";
  }

  QString TabletControllerPanel::listStyleSheet()
  {
    return "QListWidget {font-size: 20pt; color: #424242;}";
  }

  TabletControllerPanel::TabletControllerPanel(QWidget* parent): rviz::Panel(parent)
  {
    ros::NodeHandle nh;
    pub_start_demo_ = nh.advertise<roseus::StringStamped>(
      "/Tablet/StartDemo", 1);
    pub_spot_ = nh.advertise<roseus::StringStamped>(
      "/Tablet/MoveToSpot", 1);
    sub_spots_ = nh.subscribe("/spots_marker_array",
                              1, &TabletControllerPanel::spotCallback, this);
    pub_cmd_vel_ = nh.advertise<geometry_msgs::Twist>(
      "/navigation/unsafe_vel", 1);
    layout_ = new QVBoxLayout();
    layout_->addStretch();
    task_button_ = new QPushButton("Task", this);
    task_button_->setMinimumHeight(100);
    task_button_->setStyleSheet(defaultButtonStyleSheet());

    connect(task_button_, SIGNAL(released()), this, SLOT(taskButtonClicked()));
    layout_->addWidget(task_button_);
    layout_->addSpacing(10);
    spot_button_ = new QPushButton("Move to spot", this);
    spot_button_->setMinimumHeight(100);
    spot_button_->setStyleSheet(defaultButtonStyleSheet());
    connect(spot_button_, SIGNAL(released()), this, SLOT(spotButtonClicked()));
    layout_->addWidget(spot_button_);
    layout_->addSpacing(10);
    cmd_vel_area_ = new TabletCmdVelArea(this, pub_cmd_vel_);
    layout_->addWidget(cmd_vel_area_);

    setLayout(layout_);
    setBackgroundRole(QPalette::Base);
    setAutoFillBackground(true);
  }

  TabletControllerPanel::~TabletControllerPanel()
  {

  }

  void TabletControllerPanel::load(const rviz::Config& config)
  {
    rviz::Panel::load(config);
  }

  void TabletControllerPanel::save(rviz::Config config) const
  {
    rviz::Panel::save(config);
  }

  ////////////////////////////////////////////////////////
  // callbacks
  ////////////////////////////////////////////////////////
  void TabletControllerPanel::spotCallback(
    const visualization_msgs::MarkerArray::ConstPtr& marker)
  {
    boost::mutex::scoped_lock lock(mutex_);
    spots_.clear();
    for (size_t i = 0; i < marker->markers.size(); i++) {
      std::string text = marker->markers[i].text;
      if (!text.empty()) {
        spots_.push_back(text);
      }
    }
  }

  void TabletControllerPanel::taskButtonClicked()
  {
    task_dialog_ = new QDialog();
    task_dialog_->setBackgroundRole(QPalette::Base);
    task_dialog_->setAutoFillBackground(true);
    task_dialog_layout_ = new QVBoxLayout();
    task_radio_buttons_.clear();
    std::vector<std::string> tasks;
    tasks.push_back("/Tablet/other/GetGeorgia");
    tasks.push_back("/Tablet/chen/GoToElevator");
    tasks.push_back("/Tablet/chen/Greeting1");
    tasks.push_back("/Tablet/chen/Greeting2");
    tasks.push_back("/Tablet/chen/Greeting3");
    tasks.push_back("/Tablet/chen/Greeting4");
    tasks.push_back("/Tablet/chen/Greeting5");
    tasks.push_back("/Tablet/chen/HandOver");
    for (size_t i = 0; i < tasks.size(); i++) {
      QRadioButton* task = new QRadioButton(QString::fromStdString(tasks[i]), this);
      task->setMinimumHeight(50);
      if (i == 0) {
        task->setChecked(true);
      }
      task->setStyleSheet(radioButtonStyleSheet());
      task_radio_buttons_.push_back(task);
    }

    for (size_t i = 0; i < task_radio_buttons_.size(); i++) {
      task_dialog_layout_->addWidget(task_radio_buttons_[i]);
    }
    task_dialog_button_layout_ = new QHBoxLayout();
    task_execute_button_ = new QPushButton("Execute", this);
    task_execute_button_->setStyleSheet(executeButtonStyleSheet());
    task_execute_button_->setMinimumHeight(100);
    task_execute_button_->setMinimumWidth(300);
    task_dialog_button_layout_->addWidget(task_execute_button_);
    connect(task_execute_button_, SIGNAL(released()), this, SLOT(taskExecuteClicked()));
    task_cancel_button_ = new QPushButton("Cancel", this);
    task_cancel_button_->setStyleSheet(defaultButtonStyleSheet());
    task_cancel_button_->setMinimumHeight(100);
    task_cancel_button_->setMinimumWidth(300);
    connect(task_cancel_button_, SIGNAL(released()), this, SLOT(taskCancelClicked()));
    task_dialog_button_layout_->addWidget(task_cancel_button_);
    task_dialog_layout_->addLayout(task_dialog_button_layout_);
    task_dialog_->setLayout(task_dialog_layout_);
    task_dialog_->show();

  }

  void TabletControllerPanel::taskCancelClicked()
  {
    task_dialog_->reject();
  }

  void TabletControllerPanel::taskExecuteClicked()
  {
    for (size_t i = 0; i < task_radio_buttons_.size(); i++) {
      QRadioButton* radio = task_radio_buttons_[i];
      if (radio->isChecked()) {
        std::string task = radio->text().toStdString();
        ROS_INFO("task: %s", task.c_str());
        task_dialog_->reject();
        roseus::StringStamped command;
        command.data = task;
        command.header.stamp = ros::Time::now();
        pub_start_demo_.publish(command);
        return;
      }
    }
  }

  void TabletControllerPanel::spotButtonClicked()
  {
    boost::mutex::scoped_lock lock(mutex_);
    spot_dialog_ = new QDialog();
    spot_dialog_->setBackgroundRole(QPalette::Base);
    spot_dialog_->setAutoFillBackground(true);
    spot_dialog_layout_ = new QVBoxLayout();

    spot_list_ = new QListWidget();
    spot_list_->setSortingEnabled(true);
    spot_list_->setStyleSheet(listStyleSheet());
    for (size_t i = 0; i < spots_.size(); i++) {
      QListWidgetItem* item = new QListWidgetItem(
        QString::fromStdString(spots_[i]));
      item->setSizeHint(QSize(item->sizeHint().width(), 30));
      spot_list_->addItem(item);
    }
    spot_dialog_layout_->addWidget(spot_list_);
    spot_dialog_button_layout_ = new QHBoxLayout();
    spot_go_button_ = new QPushButton("Go", this);
    spot_go_button_->setStyleSheet(executeButtonStyleSheet());
    spot_go_button_->setMinimumHeight(50);
    spot_go_button_->setMinimumWidth(300);
    connect(spot_go_button_, SIGNAL(released()),
            this, SLOT(spotGoClicked()));
    spot_dialog_button_layout_->addWidget(spot_go_button_);

    spot_cancel_button_ = new QPushButton("Cancel", this);
    spot_cancel_button_->setMinimumHeight(50);
    spot_cancel_button_->setMinimumWidth(300);
    spot_cancel_button_->setStyleSheet(defaultButtonStyleSheet());
    connect(spot_cancel_button_, SIGNAL(released()),
            this, SLOT(spotCancelClicked()));
    spot_dialog_button_layout_->addWidget(spot_cancel_button_);
    spot_dialog_layout_->addLayout(spot_dialog_button_layout_);
    spot_dialog_->setLayout(spot_dialog_layout_);
    spot_dialog_->show();
  }

  void TabletControllerPanel::spotCancelClicked()
  {
    spot_dialog_->reject();
  }

  void TabletControllerPanel::spotGoClicked()
  {
    QListWidgetItem* item = spot_list_->currentItem();
    if (item) {
      std::string spot = item->text().toStdString();
      roseus::StringStamped spot_command;
      spot_command.data = spot;
      spot_command.header.stamp = ros::Time::now();
      pub_spot_.publish(spot_command);
    }
    spot_dialog_->reject();
  }

}

#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS (jsk_rviz_plugins::TabletControllerPanel, rviz::Panel);


text:
//
// Copyright 2013 Christian Henning
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//
#include <boost/gil.hpp>
#include <boost/gil/extension/io/bmp.hpp>

#include <boost/core/lightweight_test.hpp>

#include "paths.hpp"
#include "scanline_read_test.hpp"

#include <string>

namespace gil = boost::gil;
namespace mp11 = boost::mp11;

template <typename Image> void write(Image &img, std::string const &file_name) {
  write_view(bmp_out + file_name, gil::view(img), gil::bmp_tag());
}

void test_read_header() {
  using backend_t =
      gil::get_reader_backend<std::string const, gil::bmp_tag>::type;
  backend_t backend = gil::read_image_info(bmp_filename, gil::bmp_tag());
  BOOST_TEST_EQ(backend._info._offset, 54u);
  BOOST_TEST_EQ(backend._info._header_size, 40u);
  BOOST_TEST_EQ(backend._info._width, 1000);
  BOOST_TEST_EQ(backend._info._height, 600);
  BOOST_TEST_EQ(backend._info._bits_per_pixel, 24);
  BOOST_TEST_EQ(backend._info._compression, 0u);
  BOOST_TEST_EQ(backend._info._image_size, 1800000u);
  BOOST_TEST_EQ(backend._info._horizontal_resolution, 0);
  BOOST_TEST_EQ(backend._info._vertical_resolution, 0);
  BOOST_TEST_EQ(backend._info._num_colors, 0u);
  BOOST_TEST_EQ(backend._info._num_important_colors, 0u);
  BOOST_TEST_EQ(backend._info._valid, true);
}

#ifdef BOOST_GIL_IO_USE_BMP_TEST_SUITE_IMAGES
void test_read_reference_images_test() {
  // comments are taken from
  // http://entropymine.com/jason/bmpsuite/reference/reference.html

  // g01bw.bmp - black and white palette (#000000,#FFFFFF)
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g01bw.bmp", img, gil::bmp_tag());
  }

  // g01wb.bmp - white and black palette (#FFFFFF,#000000).
  // Should look the same as g01bw, not inverted.
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g01wb.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g01wb.bmp");
  }

  // g01bg.bmp - blue and green palette (#4040FF,#40FF40)
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g01bg.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g01bg.bmp");
  }

  // g01p1.bmp - 1-color (blue) palette (#4040FF)
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g01p1.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g01p1.bmp");
  }

  // g04.bmp - basic 4bpp (16 color) image
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g04.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);
    write(img, "g04.bmp");
  }

  // g04rle.bmp - RLE compressed.
  {
    gil::rgb8_image_t img;
    gil::read_image(bmp_in + "g04rle.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g04rle.bmp");
  }

  // g04p4.bmp - 4-color grayscale palette
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g04p4.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g04p4.bmp");
  }

  // g08.bmp - basic 8bpp (256 color) image
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08.bmp");
  }

  // g08p256.bmp - biClrUsed=256, biClrImportant=0 [=256]
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08p256.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08p256.bmp");
  }

  // g08pi256.bmp - biClrUsed=256, biClrImportant=256
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08pi256.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08pi256.bmp");
  }

  // g08pi64.bmp - biClrUsed=256, biClrImportant=64. It's barely possible that
  // some sophisticated viewers may display this image in grayscale, if there
  // are a limited number of colors available.
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08pi64.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08pi64.bmp");
  }

  // g08rle.bmp - RLE compressed.
  {
    gil::rgb8_image_t img;
    gil::read_image(bmp_in + "g08rle.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08rle.bmp");
  }

  // g08os2.bmp - OS/2-style bitmap. This is an obsolete variety of BMP
  // that is still encountered sometimes. It has 3-byte palette
  // entries (instead of 4), and 16-bit width/height fields (instead of 32).
  {
    gil::rgb8_image_t img;
    gil::read_image(bmp_in + "g08os2.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08os2.bmp");
  }

  // g08res22.bmp - resolution 7874x7874 pixels/meter (200x200 dpi)
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08res22.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08res22.bmp");
  }

  // g08res11.bmp - resolution 3937x3937 pixels/meter (100x100 dpi)
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08res11.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08res11.bmp");
  }

  // g08res21.bmp resolution 7874x3937 pixels/meter (200x100 dpi).
  // Some programs (e.g. Imaging for Windows) may display this image
  // stretched vertically, which is the optimal thing to do if the
  // program is primarily a viewer, rather than an editor.
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08res21.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08res21.bmp");
  }

  // g08s0.bmp - bits size not given (set to 0). This is legal for uncompressed
  // bitmaps.
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08s0.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08s0.bmp");
  }

  // g08offs.bmp - bfOffBits in header not set to the usual value.
  // There are 100 extra unused bytes between palette and bits.
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08offs.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08offs.bmp");
  }

  // g08w126.bmp - size 126x63 (right and bottom slightly clipped)
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08w126.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 126u);
    BOOST_TEST_EQ(view(img).height(), 63u);

    write(img, "g08w126.bmp");
  }

  // g08w125.bmp - size 125x62
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08w125.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 125u);
    BOOST_TEST_EQ(view(img).height(), 62u);

    write(img, "g08w125.bmp");
  }

  // g08w124.bmp - size 124x61
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08w124.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 124u);
    BOOST_TEST_EQ(view(img).height(), 61u);

    write(img, "g08w124.bmp");
  }

  // g08p64.bmp - 64-color grayscale palette
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g08p64.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g08p64.bmp");
  }

  // g16def555.bmp - 15-bit color (1 bit wasted), biCompression=BI_RGB (no
  // bitfields, defaults to 5-5-5)
  {
    gil::rgb8_image_t img;
    gil::read_image(bmp_in + "g16def555.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g16def555.bmp");
  }

  // g16bf555.bmp - 15-bit color, biCompression=BI_BITFIELDS (bitfields indicate
  // 5-5-5)
  {
    gil::rgb8_image_t img;
    gil::read_image(bmp_in + "g16bf555.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g16bf555.bmp");
  }

  // g16bf565.bmp - 16-bit color, biCompression=BI_BITFIELDS (bitfields indicate
  // 5-6-5)
  {
    gil::rgb8_image_t img;
    gil::read_image(bmp_in + "g16bf565.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g16bf565.bmp");
  }

  // g24.bmp - 24-bit color (BGR)
  {
    gil::rgb8_image_t img;
    gil::read_image(bmp_in + "g24.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g24.bmp");
  }

  // g32def.bmp - 24-bit color (8 bits wasted), biCompression=BI_RGB (no
  // bitfields, defaults to BGRx)
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g32def.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g32def.bmp");
  }

  // g32bf.bmp - 24-bit color (8 bits wasted), biCompression=BI_BITFIELDS
  // (bitfields indicate BGRx)
  {
    gil::rgba8_image_t img;
    gil::read_image(bmp_in + "g32bf.bmp", img, gil::bmp_tag());
    BOOST_TEST_EQ(view(img).width(), 127u);
    BOOST_TEST_EQ(view(img).height(), 64u);

    write(img, "g32bf.bmp");
  }
}

void test_read_reference_images_image_iterator() {
  // comments are taken from
  // http://entropymine.com/jason/bmpsuite/reference/reference.html

  // g01bw.bmp - black and white palette (#000000,#FFFFFF)
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g01bw.bmp").c_str());

  // g01wb.bmp - white and black palette (#FFFFFF,#000000).
  // Should look the same as g01bw, not inverted.
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g01wb.bmp").c_str());

  // g01bg.bmp - blue and green palette (#4040FF,#40FF40)
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g01bg.bmp").c_str());

  // g01p1.bmp - 1-color (blue) palette (#4040FF)
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g01p1.bmp").c_str());

  // g04.bmp - basic 4bpp (16 color) image
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g04.bmp").c_str());

  // not supported
  // g04rle.bmp - RLE compressed.
  // test_scanline_reader<gil::bgra8_image_t, gil::bmp_tag>(std::string(bmp_in +
  // "g01bg.bmp").c_str());

  // g04p4.bmp - 4-color grayscale palette
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g04p4.bmp").c_str());

  // g08.bmp - basic 8bpp (256 color) image
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08.bmp").c_str());

  // g08p256.bmp - biClrUsed=256, biClrImportant=0 [=256]
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08p256.bmp").c_str());

  // g08pi256.bmp - biClrUsed=256, biClrImportant=256
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08pi256.bmp").c_str());

  // g08pi64.bmp - biClrUsed=256, biClrImportant=64. It's barely possible that
  // some sophisticated viewers may display this image in grayscale, if there
  // are a limited number of colors available.
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08pi64.bmp").c_str());

  // not supported
  // g08rle.bmp - RLE compressed.

  // g08os2.bmp - OS/2-style bitmap. This is an obsolete variety of BMP
  // that is still encountered sometimes. It has 3-byte palette
  // entries (instead of 4), and 16-bit width/height fields (instead of 32).
  test_scanline_reader<gil::rgb8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08os2.bmp").c_str());

  // g08res22.bmp - resolution 7874x7874 pixels/meter (200x200 dpi)
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08res22.bmp").c_str());

  // g08res11.bmp - resolution 3937x3937 pixels/meter (100x100 dpi)
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08res11.bmp").c_str());

  // g08res21.bmp resolution 7874x3937 pixels/meter (200x100 dpi).
  // Some programs (e.g. Imaging for Windows) may display this image
  // stretched vertically, which is the optimal thing to do if the
  // program is primarily a viewer, rather than an editor.
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08res21.bmp").c_str());

  // g08s0.bmp - bits size not given (set to 0). This is legal for uncompressed
  // bitmaps.
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08s0.bmp").c_str());

  // g08offs.bmp - bfOffBits in header not set to the usual value.
  // There are 100 extra unused bytes between palette and bits.
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08offs.bmp").c_str());

  // g08w126.bmp - size 126x63 (right and bottom slightly clipped)
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08w126.bmp").c_str());

  // g08w125.bmp - size 125x62
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08w125.bmp").c_str());

  // g08w124.bmp - size 124x61
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08w124.bmp").c_str());

  // g08p64.bmp - 64-color grayscale palette
  test_scanline_reader<gil::rgba8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g08p64.bmp").c_str());

  // g16def555.bmp - 15-bit color (1 bit wasted), biCompression=BI_RGB (no
  // bitfields, defaults to 5-5-5)
  test_scanline_reader<gil::rgb8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g16def555.bmp").c_str());

  // g16bf555.bmp - 15-bit color, biCompression=BI_BITFIELDS (bitfields indicate
  // 5-5-5)
  test_scanline_reader<gil::rgb8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g16bf555.bmp").c_str());

  // g16bf565.bmp - 16-bit color, biCompression=BI_BITFIELDS (bitfields indicate
  // 5-6-5)
  test_scanline_reader<gil::rgb8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g16bf565.bmp").c_str());

  // g24.bmp - 24-bit color (BGR)
  test_scanline_reader<gil::bgr8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g24.bmp").c_str());

  // g32def.bmp - 24-bit color (8 bits wasted), biCompression=BI_RGB (no
  // bitfields, defaults to BGRx)
  test_scanline_reader<gil::bgra8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g32def.bmp").c_str());

  // g32bf.bmp - 24-bit color (8 bits wasted), biCompression=BI_BITFIELDS
  // (bitfields indicate BGRx)
  test_scanline_reader<gil::bgra8_image_t, gil::bmp_tag>(
      std::string(bmp_in + "g32bf.bmp").c_str());
}

void test_partial_image() {
  std::string const filename(bmp_in + "g24.bmp");

  gil::rgb8_image_t img;
  gil::read_image(filename, img,
                  gil::image_read_settings<gil::bmp_tag>(gil::point_t(0, 0),
                                                         gil::point_t(50, 50)));

  write_view(bmp_out + "rgb_partial.bmp", gil::view(img), gil::bmp_tag());
}
#endif // BOOST_GIL_IO_USE_BMP_TEST_SUITE_IMAGES

int main(int argc, char *argv[]) {
  try {
    test_read_header();

#ifdef BOOST_GIL_IO_USE_BMP_TEST_SUITE_IMAGES
    test_read_reference_images_test();
    test_read_reference_images_image_iterator();
    test_partial_image();
#endif
  } catch (std::exception const &e) {
    BOOST_ERROR(e.what());
  }
  return boost::report_errors();
}

max_stars_repo_name: sdebionne/gil-reformated

max_issues_repo_name: sdebionne/gil-reformated

max_forks_repo_name: sdebionne/gil-reformated


text:
#include <iomanip>
#include <iostream>

#include <boost/math/special_functions/bessel.hpp>
#include <boost/math/special_functions/cbrt.hpp>
#include <boost/math/special_functions/gamma.hpp>

#include <boost/math/bindings/e_float.hpp>
#include <boost/multiprecision/number.hpp>

#include "examples.h"

typedef boost::multiprecision::number<boost::math::ef::e_float,
                                      boost::multiprecision::et_off>
e_float_type;

void examples::nr_021::boost_bindings()
{
  const e_float_type y(char(1));
  const e_float_type x(3.0L);
  const e_float_type z(y / x);

  const e_float_type a(z);

  const e_float_type lg_max = log((std::numeric_limits<e_float_type>::max)());
  const e_float_type eps    = std::numeric_limits<e_float_type>::epsilon();

  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << e_float_type(0.5F)                 << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << z                                  << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << sin  (z)                           << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << floor(e_float_type(1.5F))          << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << ceil (e_float_type(1.5F))          << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << sqrt (e_float_type(2))             << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << boost::math::cbrt(e_float_type(2)) << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << exp  (z)                           << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << atan2(y, x)                        << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << atan (z)                           << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << log  (e_float_type(2))             << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << lg_max                             << std::endl;

  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << eps                    << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << sqrt(eps)              << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << boost::math::cbrt(eps) << std::endl;

  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << boost::math::tgamma(e_float_type(0.5F))          << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << sqrt(boost::math::constants::pi<e_float_type>()) << std::endl;

  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << boost::math::cyl_bessel_j(e_float_type(1) / 7, e_float_type(2.5F))  << std::endl;

  int nexp;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << frexp(e_float_type(2), &nexp) << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10) << ldexp(e_float_type(1), 2)     << std::endl;

  std::cout << std::boolalpha << (x < y) << std::endl;
  std::cout << std::boolalpha << (y > x) << std::endl;

  std::cout << std::boolalpha << (lg_max > (std::numeric_limits<std::int64_t>::max)()) << std::endl;
  std::cout << std::boolalpha << (lg_max > (std::numeric_limits<std::int64_t>::min)()) << std::endl;

  const e_float_type a3(("33." + std::string(std::size_t(std::numeric_limits<e_float_type>::digits10 - 2), char('3')) + std::string(16U, char('4'))).c_str());
  const e_float_type b3(e_float_type(100U) / 3);

  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10 + 4) << a3 << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10 + 4) << b3 << std::endl;

  std::cout << std::boolalpha << (a3 == b3) << std::endl;
  std::cout << std::boolalpha << (a3 >  b3) << std::endl;
  std::cout << std::boolalpha << (a3 <  b3) << std::endl;

  const e_float_type c3(("33." + std::string(120U, char('3'))).c_str());
  const e_float_type d3(e_float_type(100U) / 3);

  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10 + 4) << c3 << std::endl;
  std::cout << std::setprecision(std::numeric_limits<e_float_type>::digits10 + 4) << d3 << std::endl;

  std::cout << std::boolalpha << (c3 == d3) << std::endl;
  std::cout << std::boolalpha << (c3 >  d3) << std::endl;
  std::cout << std::boolalpha << (c3 <  d3) << std::endl;
}