#include <pybind11/functional.h>
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include <algorithm>
#include <sstream>
#include <stdexcept>
#include <unordered_set>

#define BOOST_POLYGON_NO_DEPS
#define BOOST_NO_USER_CONFIG
#define BOOST_NO_COMPILER_CONFIG
#define BOOST_NO_STDLIB_CONFIG
#define BOOST_NO_PLATFORM_CONFIG
#define BOOST_HAS_STDINT_H

#define __GLIBC__ 0

#define private public

#include <boost/polygon/voronoi.hpp>

namespace py = pybind11;

#define MODULE_NAME _voronoi
#define C_STR_HELPER(a) #a
#define C_STR(a) C_STR_HELPER(a)
#define BEACH_LINE_KEY_NAME "BeachLineKey"
#define BEACH_LINE_VALUE_NAME "BeachLineValue"
#define BIG_FLOAT_NAME "BigFloat"
#define BIG_INT_NAME "BigInt"
#define CIRCLE_EVENT_NAME "CircleEvent"
#define COMPARISON_RESULT_NAME "ComparisonResult"
#define GEOMETRY_CATEGORY_NAME "GeometryCategory"
#define ORIENTATION_NAME "Orientation"
#define POINT_NAME "Point"
#define ROBUST_DIFFERENCE_NAME "RobustDifference"
#define ROBUST_FLOAT_NAME "RobustFloat"
#define SEGMENT_NAME "Segment"
#define SITE_EVENT_NAME "SiteEvent"
#define SOURCE_CATEGORY_NAME "SourceCategory"
#define BUILDER_NAME "Builder"
#define CELL_NAME "Cell"
#define DIAGRAM_NAME "Diagram"
#define EDGE_NAME "Edge"
#define VERTEX_NAME "Vertex"
#ifndef VERSION_INFO
#define VERSION_INFO "dev"
#endif

using coordinate_t = boost::polygon::detail::int32;
using Builder = boost::polygon::default_voronoi_builder;
using Diagram = boost::polygon::voronoi_diagram<double>;
using Cell = boost::polygon::voronoi_cell<double>;
using CircleEvent = boost::polygon::detail::circle_event<double>;
using UlpComparator = boost::polygon::detail::ulp_comparison<double>;
using ComparisonResult = UlpComparator::Result;
using CTypeTraits = boost::polygon::detail::voronoi_ctype_traits<coordinate_t>;
using BigFloat = CTypeTraits::efpt_type;
using BigInt = CTypeTraits::big_int_type;
using Edge = boost::polygon::voronoi_edge<double>;
using GeometryCategory = boost::polygon::GeometryCategory;
using Point = boost::polygon::detail::point_2d<coordinate_t>;
using RobustFloat = boost::polygon::detail::robust_fpt<double>;
using RobustDifference = boost::polygon::detail::robust_dif<RobustFloat>;
using RobustSumExpression = boost::polygon::detail::robust_sqrt_expr<
    BigInt, BigFloat, boost::polygon::detail::type_converter_efpt>;
using SiteEvent = boost::polygon::detail::site_event<coordinate_t>;
using BeachLineKey = boost::polygon::detail::beach_line_node_key<SiteEvent>;
using SourceCategory = boost::polygon::SourceCategory;
using BeachLineValue =
    boost::polygon::detail::beach_line_node_data<Edge, CircleEvent>;
using Predicates = boost::polygon::detail::voronoi_predicates<CTypeTraits>;
using EventComparisonPredicate =
    Predicates::event_comparison_predicate<SiteEvent, CircleEvent>;
using Orientation = Predicates::orientation_test::Orientation;
using Vertex = boost::polygon::voronoi_vertex<double>;

static int to_sign(coordinate_t value) {
  return value > 0 ? 1 : (value < 0 ? -1 : 0);
}

static std::string bool_repr(bool value) { return py::str(py::bool_(value)); }

template <class Object>
std::string to_repr(const Object& object) {
  std::ostringstream stream;
  stream.precision(std::numeric_limits<double>::digits10 + 2);
  stream << object;
  return stream.str();
}

template <class Object>
static void write_pointer(std::ostream& stream, Object* value) {
  if (value == nullptr)
    stream << py::none();
  else
    stream << *value;
}

template <typename Sequence>
static void write_sequence(std::ostream& stream, const Sequence& sequence) {
  stream << "[";
  if (!sequence.empty()) {
    stream << sequence[0];
    std::for_each(std::next(std::begin(sequence)), std::end(sequence),
                  [&stream](const typename Sequence::value_type& value) {
                    stream << ", " << value;
                  });
  }
  stream << "]";
};

struct Segment {
  Point start, end;
  Segment(const Point& start_, const Point& end_) : start(start_), end(end_) {}
};

namespace boost {
namespace polygon {
static std::ostream& operator<<(std::ostream& stream,
                                const SourceCategory& source_category) {
  stream << C_STR(MODULE_NAME) "." SOURCE_CATEGORY_NAME ".";
  switch (source_category) {
    case SourceCategory::SOURCE_CATEGORY_SINGLE_POINT:
      stream << "SINGLE_POINT";
      break;
    case SourceCategory::SOURCE_CATEGORY_SEGMENT_START_POINT:
      stream << "SEGMENT_START_POINT";
      break;
    case SourceCategory::SOURCE_CATEGORY_SEGMENT_END_POINT:
      stream << "SEGMENT_END_POINT";
      break;
    case SourceCategory::SOURCE_CATEGORY_INITIAL_SEGMENT:
      stream << "INITIAL_SEGMENT";
      break;
    case SourceCategory::SOURCE_CATEGORY_REVERSE_SEGMENT:
      stream << "REVERSE_SEGMENT";
      break;
    default:
      stream << "???";
      break;
  }
  return stream;
}

static std::ostream& operator<<(std::ostream& stream, const Builder& builder) {
  stream << C_STR(MODULE_NAME) "." BUILDER_NAME "(" << builder.index_ << ", ";
  write_sequence(stream, builder.site_events_);
  return stream << ")";
}

static std::ostream& operator<<(std::ostream& stream, const Cell& cell) {
  return stream << C_STR(MODULE_NAME) "." CELL_NAME "(" << cell.source_index()
                << ", " << cell.source_category() << ")";
}

static std::ostream& operator<<(std::ostream& stream, const Vertex& vertex) {
  return stream << C_STR(MODULE_NAME) "." VERTEX_NAME "(" << vertex.x() << ", "
                << vertex.y() << ")";
}

static std::ostream& operator<<(std::ostream& stream, const Edge& edge) {
  stream << C_STR(MODULE_NAME) "." EDGE_NAME "(";
  write_pointer(stream, edge.vertex0());
  stream << ", ";
  write_pointer(stream, edge.cell());
  return stream << ", " << bool_repr(edge.is_linear()) << ", "
                << bool_repr(edge.is_primary()) << ")";
}

static bool operator==(const Vertex& left, const Vertex& right) {
  static const voronoi_diagram_traits<double>::vertex_equality_predicate_type
      comparator;
  return comparator(left, right);
}

static std::ostream& operator<<(std::ostream& stream, const Diagram& diagram) {
  stream << C_STR(MODULE_NAME) "." DIAGRAM_NAME "(";
  write_sequence(stream, diagram.cells_);
  stream << ", ";
  write_sequence(stream, diagram.edges_);
  stream << ", ";
  write_sequence(stream, diagram.vertices_);
  return stream << ")";
}

namespace detail {
static std::ostream& operator<<(std::ostream& stream, const BigFloat& float_) {
  return stream << C_STR(MODULE_NAME) "." BIG_FLOAT_NAME "(" << float_.val_
                << ", " << float_.exp_ << ")";
}

static std::ostream& operator<<(std::ostream& stream, const BigInt& int_) {
  stream << C_STR(MODULE_NAME) "." BIG_INT_NAME "(" << to_sign(int_.count())
         << ", [";
  std::size_t size = int_.size();
  const auto& chunks = int_.chunks();
  if (size != 0) {
    stream << chunks[0];
    for (std::size_t index = 1; index < size; ++index)
      stream << ", " << chunks[index];
  }
  return stream << "])";
}

static bool operator==(const CircleEvent& left, const CircleEvent& right) {
  return left.x() == right.x() && left.y() == right.y() &&
         left.lower_x() == right.lower_x() &&
         left.is_active() == right.is_active();
}

static std::ostream& operator<<(std::ostream& stream,
                                const CircleEvent& event) {
  return stream << C_STR(MODULE_NAME) "." CIRCLE_EVENT_NAME "(" << event.x()
                << ", " << event.y() << ", " << event.lower_x() << ", "
                << bool_repr(event.is_active()) << ")";
}

static std::ostream& operator<<(std::ostream& stream, const Point& point) {
  return stream << C_STR(MODULE_NAME) "." POINT_NAME "(" << point.x() << ", "
                << point.y() << ")";
}

static std::ostream& operator<<(std::ostream& stream,
                                const RobustFloat& float_) {
  return stream << C_STR(MODULE_NAME) "." ROBUST_FLOAT_NAME "(" << float_.fpv()
                << ", " << float_.re() << ")";
}

static std::ostream& operator<<(std::ostream& stream,
                                const RobustDifference& difference) {
  return stream << C_STR(MODULE_NAME) "." ROBUST_DIFFERENCE_NAME "("
                << difference.pos() << ", " << difference.neg() << ")";
}

static std::ostream& operator<<(std::ostream& stream, const SiteEvent& event) {
  return stream << C_STR(MODULE_NAME) "." SITE_EVENT_NAME "(" << event.point0()
                << ", " << event.point1() << ", " << event.sorted_index()
                << ", " << event.initial_index() << ", "
                << bool_repr(event.is_inverse()) << ", "
                << event.source_category() << ")";
}

static std::ostream& operator<<(std::ostream& stream, const BeachLineKey& key) {
  return stream << C_STR(MODULE_NAME) "." BEACH_LINE_KEY_NAME "("
                << key.left_site() << ", " << key.right_site() << ")";
}

static std::ostream& operator<<(std::ostream& stream,
                                const BeachLineValue& value) {
  stream << C_STR(MODULE_NAME) "." BEACH_LINE_VALUE_NAME "(";
  write_pointer(stream, value.edge());
  stream << ", ";
  write_pointer(stream, value.circle_event());
  return stream << ")";
}
}  // namespace detail

template <>
struct geometry_concept<Point> {
  typedef point_concept type;
};

template <>
struct point_traits<Point> {
  typedef int coordinate_type;

  static inline coordinate_type get(const Point& point, orientation_2d orient) {
    return (orient == HORIZONTAL) ? point.x() : point.y();
  }
};

template <>
struct geometry_concept<Segment> {
  typedef segment_concept type;
};

template <>
struct segment_traits<Segment> {
  typedef Segment segment_type;
  typedef Point point_type;
  typedef int coordinate_type;

  static point_type get(const segment_type& segment, direction_1d dir) {
    return dir.to_int() ? segment.end : segment.start;
  }
};
}  // namespace polygon
}  // namespace boost

static std::ostream& operator<<(std::ostream& stream, const Segment& segment) {
  return stream << C_STR(MODULE_NAME) "." SEGMENT_NAME "(" << segment.start
                << ", " << segment.end << ")";
}

static bool operator==(const Segment& left, const Segment& right) {
  return left.start == right.start && left.end == right.end;
}

PYBIND11_MODULE(MODULE_NAME, m) {
  m.doc() = R"pbdoc(Python binding of boost/polygon library.)pbdoc";
  m.attr("__version__") = C_STR(VERSION_INFO);

  py::enum_<ComparisonResult>(m, COMPARISON_RESULT_NAME)
      .value("LESS", ComparisonResult::LESS)
      .value("EQUAL", ComparisonResult::EQUAL)
      .value("MORE", ComparisonResult::MORE);

  py::enum_<GeometryCategory>(m, GEOMETRY_CATEGORY_NAME)
      .value("POINT", GeometryCategory::GEOMETRY_CATEGORY_POINT)
      .value("SEGMENT", GeometryCategory::GEOMETRY_CATEGORY_SEGMENT);

  py::enum_<Orientation>(m, ORIENTATION_NAME)
      .value("RIGHT", Orientation::RIGHT)
      .value("COLLINEAR", Orientation::COLLINEAR)
      .value("LEFT", Orientation::LEFT);

  py::enum_<SourceCategory>(m, SOURCE_CATEGORY_NAME)
      .value("SINGLE_POINT", SourceCategory::SOURCE_CATEGORY_SINGLE_POINT)
      .value("SEGMENT_START_POINT",
             SourceCategory::SOURCE_CATEGORY_SEGMENT_START_POINT)
      .value("SEGMENT_END_POINT",
             SourceCategory::SOURCE_CATEGORY_SEGMENT_END_POINT)
      .value("INITIAL_SEGMENT", SourceCategory::SOURCE_CATEGORY_INITIAL_SEGMENT)
      .value("REVERSE_SEGMENT", SourceCategory::SOURCE_CATEGORY_REVERSE_SEGMENT)
      .def("belongs", &boost::polygon::belongs);

  py::class_<BeachLineKey>(m, BEACH_LINE_KEY_NAME)
      .def(py::init<SiteEvent>(), py::arg("site"))
      .def(py::init<SiteEvent, SiteEvent>(), py::arg("left_site"),
           py::arg("right_site"))
      .def("__repr__", to_repr<BeachLineKey>)
      .def(
          "__lt__",
          [](const BeachLineKey& self, const BeachLineKey& other) {
            static const Predicates::node_comparison_predicate<BeachLineKey>
                comparator;
            return comparator(self, other);
          },
          py::is_operator())
      .def(
          "to_comparison_y",
          [](const BeachLineKey& self, bool is_new_node) {
            static const Predicates::node_comparison_predicate<BeachLineKey>
                comparator;
            return comparator.get_comparison_y(self, is_new_node);
          },
          py::arg("is_new_node") = true)
      .def_property_readonly(
          "comparison_site",
          [](const BeachLineKey& self) {
            static const Predicates::node_comparison_predicate<BeachLineKey>
                comparator;
            return comparator.get_comparison_site(self);
          })
      .def_property_readonly(
          "left_site",
          [](const BeachLineKey& self) { return self.left_site(); })
      .def_property_readonly("right_site", [](const BeachLineKey& self) {
        return self.right_site();
      });

  py::class_<BeachLineValue>(m, BEACH_LINE_VALUE_NAME)
      .def(py::init([](Edge* edge, CircleEvent* circle_event) {
             return BeachLineValue{edge}.circle_event(circle_event);
           }),
           py::arg("edge"), py::arg("circle_event") = nullptr)
      .def("__repr__", to_repr<BeachLineValue>)
      .def_property_readonly(
          "edge", [](const BeachLineValue& self) { return self.edge(); })
      .def_property_readonly("circle_event", [](const BeachLineValue& self) {
        return self.circle_event();
      });

  py::class_<BigFloat>(m, BIG_FLOAT_NAME)
      .def(py::init<double, int>(), py::arg("mantissa"), py::arg("exponent"))
      .def(-py::self)
      .def(py::self + py::self)
      .def(py::self - py::self)
      .def(py::self * py::self)
      .def(py::self / py::self)
      .def(py::self += py::self)
      .def(py::self -= py::self)
      .def(py::self *= py::self)
      .def(py::self /= py::self)
      .def("__bool__",
           [](const BigFloat& self) {
             return !boost::polygon::detail::is_zero(self);
           })
      .def("__float__", &BigFloat::d)
      .def("__repr__", to_repr<BigFloat>)
      .def("sqrt", &BigFloat::sqrt)
      .def_readonly("exponent", &BigFloat::exp_)
      .def_readonly("mantissa", &BigFloat::val_);

  py::class_<BigInt>(m, BIG_INT_NAME)
      .def(py::init<boost::polygon::detail::int64>(), py::arg("value"))
      .def(py::init<>(
               [](std::int8_t sign, const std::vector<std::uint32_t>& digits) {
                 auto result = std::make_unique<BigInt>();
                 result->count_ = to_sign(sign) * digits.size();
                 std::copy(digits.begin(), digits.end(), result->chunks_);
                 return result;
               }),
           py::arg("sign"), py::arg("digits"))
      .def(-py::self)
      .def(py::self + py::self)
      .def(py::self - py::self)
      .def(py::self * py::self)
      .def(py::self + coordinate_t())
      .def(py::self - coordinate_t())
      .def(py::self * coordinate_t())
      .def("__bool__",
           [](const BigInt& self) {
             return !boost::polygon::detail::is_zero(self);
           })
      .def("__float__", &BigInt::d)
      .def("__repr__", to_repr<BigInt>)
      .def("frexp", &BigInt::p)
      .def_property_readonly(
          "digits",
          [](const BigInt& self) -> std::vector<std::uint32_t> {
            std::size_t size = self.size();
            std::vector<std::uint32_t> result;
            const auto& chunks = self.chunks();
            for (std::size_t index = 0; index < size; ++index)
              result.push_back(chunks[index]);
            return result;
          })
      .def_property_readonly("sign", [](const BigInt& self) {
        const auto count = self.count();
        return to_sign(count);
      });

  py::class_<Builder>(m, BUILDER_NAME)
      .def(py::init([](std::size_t index,
                       const std::vector<SiteEvent>& site_events) {
             auto result = std::make_unique<Builder>();
             result->index_ = index;
             result->site_events_ = site_events;
             result->site_event_index_ =
                 std::numeric_limits<std::size_t>::max();
             return result;
           }),
           py::arg("index") = 0,
           py::arg("site_events") = std::vector<SiteEvent>{})
      .def("__repr__", to_repr<Builder>)
      .def("clear", &Builder::clear)
      .def("construct", &Builder::construct<Diagram>, py::arg("diagram"))
      .def("init_beach_line", &Builder::init_beach_line<Diagram>,
           py::arg("diagram"))
      .def("init_sites_queue", &Builder::init_sites_queue)
      .def("insert_new_arc",
           [](Builder& self, const SiteEvent& arc_first_site,
              const SiteEvent& arc_second_site, const SiteEvent& site,
              Diagram* diagram) {
             self.insert_new_arc(arc_first_site, arc_second_site, site,
                                 self.beach_line_.end(), diagram);
           })
      .def(
          "insert_point",
          [](Builder* builder, const Point& point) {
            return boost::polygon::insert(point, builder);
          },
          py::arg("point"))
      .def(
          "insert_segment",
          [](Builder* builder, const Segment& segment) {
            return boost::polygon::insert(segment, builder);
          },
          py::arg("segment"))
      .def("process_circle_event", &Builder::process_circle_event<Diagram>,
           py::arg("diagram"))
      .def("process_site_event", &Builder::process_site_event<Diagram>,
           py::arg("diagram"))
      .def_property_readonly(
          "beach_line",
          [](Builder& self) {
            std::vector<std::pair<BeachLineKey, BeachLineValue>> result;
            for (auto& item : self.beach_line_) {
              auto& value = item.second;
              result.push_back(
                  {item.first, BeachLineValue{static_cast<Edge*>(value.edge())}
                                   .circle_event(value.circle_event())});
            }
            return result;
          })
      .def_property_readonly(
          "end_points",
          [](const Builder& self) {
            auto queue = self.end_points_;
            std::vector<
                std::pair<Point, std::pair<BeachLineKey, BeachLineValue>>>
                result;
            while (!queue.empty()) {
              const auto element = queue.top();
              auto raw_value = element.second->second;
              result.push_back(
                  {element.first,
                   {element.second->first,
                    BeachLineValue(static_cast<Edge*>(raw_value.edge()))
                        .circle_event(raw_value.circle_event())}});
              queue.pop();
            }
            return result;
          })
      .def_property_readonly(
          "site_event_index",
          [](const Builder& self) {
            return self.site_event_index_ ==
                           std::numeric_limits<std::size_t>::max()
                       ? self.site_events_.size()
                       : self.site_event_index_;
          })
      .def_readonly("index", &Builder::index_)
      .def_readonly("site_events", &Builder::site_events_);

  py::class_<Cell, std::unique_ptr<Cell, py::nodelete>>(m, CELL_NAME)
      .def(py::init<std::size_t, SourceCategory>(), py::arg("source_index"),
           py::arg("source_category"))
      .def("__repr__", to_repr<Cell>)
      .def_property_readonly("contains_point", &Cell::contains_point)
      .def_property_readonly("contains_segment", &Cell::contains_segment)
      .def_property(
          "incident_edge",
          [](const Cell& self) { return self.incident_edge(); },
          [](Cell& self, Edge* value) { self.incident_edge(value); })
      .def_property_readonly("is_degenerate", &Cell::is_degenerate)
      .def_property_readonly("source_index", &Cell::source_index)
      .def_property_readonly("source_category", &Cell::source_category);

  py::class_<CircleEvent, std::unique_ptr<CircleEvent, py::nodelete>>(
      m, CIRCLE_EVENT_NAME)
      .def(py::init([](double center_x, double center_y, double lower_x,
                       bool is_active) {
             CircleEvent result{center_x, center_y, lower_x};
             return (is_active ? result : result.deactivate());
           }),
           py::arg("center_x"), py::arg("center_y"), py::arg("lower_x"),
           py::arg("is_active") = true)
      .def(py::self == py::self)
      .def(
          "__lt__",
          [](const CircleEvent& self, const CircleEvent& other) {
            static const EventComparisonPredicate comparator;
            return comparator(self, other);
          },
          py::is_operator())
      .def(
          "__lt__",
          [](const CircleEvent& self, const SiteEvent& other) {
            static const EventComparisonPredicate comparator;
            return comparator(self, other);
          },
          py::is_operator())
      .def("__repr__", to_repr<CircleEvent>)
      .def("deactivate", &CircleEvent::deactivate)
      .def("lies_outside_vertical_segment",
           [](const CircleEvent& self, const SiteEvent& site) {
             static Predicates::circle_formation_predicate<SiteEvent,
                                                           CircleEvent>
                 predicate;
             return predicate.lies_outside_vertical_segment(self, site);
           })
      .def_property_readonly("center_x",
                             [](const CircleEvent& self) { return self.x(); })
      .def_property_readonly("center_y",
                             [](const CircleEvent& self) { return self.y(); })
      .def_property_readonly("is_active", &CircleEvent::is_active)
      .def_property_readonly(
          "lower_x", [](const CircleEvent& self) { return self.lower_x(); })
      .def_property_readonly("lower_y", &CircleEvent::lower_y)
      .def_property_readonly("x",
                             [](const CircleEvent& self) { return self.x(); })
      .def_property_readonly("y",
                             [](const CircleEvent& self) { return self.y(); });

  py::class_<Diagram>(m, DIAGRAM_NAME)
      .def(py::init([](const std::vector<Cell>& cells,
                       const std::vector<Edge>& edges,
                       const std::vector<Vertex>& vertices) {
             auto result = std::make_unique<Diagram>();
             result->cells_ = cells;
             result->edges_ = edges;
             result->vertices_ = vertices;
             return result;
           }),
           py::arg("cells") = std::vector<Cell>{},
           py::arg("edges") = std::vector<Edge>{},
           py::arg("vertices") = std::vector<Vertex>{})
      .def("__repr__", to_repr<Diagram>)
      .def("clear", &Diagram::clear)
      .def(
          "construct",
          [](Diagram* self, const std::vector<Point>& points,
             const std::vector<Segment>& segments) {
            boost::polygon::construct_voronoi(points.begin(), points.end(),
                                              segments.begin(), segments.end(),
                                              self);
          },
          py::arg("points"), py::arg("segments"))
      .def("is_linear_edge", &Diagram::is_linear_edge<SiteEvent>,
           py::arg("first_event"), py::arg("second_event"))
      .def("is_primary_edge", &Diagram::is_primary_edge<SiteEvent>,
           py::arg("first_event"), py::arg("second_event"))
      .def("remove_edge", &Diagram::remove_edge, py::arg("edge").none(false))
      .def("_build", &Diagram::_build)
      .def(
          "_insert_new_edge",
          [](Diagram& self, const SiteEvent& first_event,
             const SiteEvent& second_event) {
            auto result = self._insert_new_edge(first_event, second_event);
            return std::make_pair(static_cast<Edge*>(result.first),
                                  static_cast<Edge*>(result.second));
          },
          py::arg("first_event"), py::arg("second_event"))
      .def(
          "_insert_new_edge_from_intersection",
          [](Diagram& self, const SiteEvent& first_site_event,
             const SiteEvent& second_site_event,
             const CircleEvent& circle_event, Edge* first_bisector,
             Edge* second_bisector) {
            auto result = self._insert_new_edge(
                first_site_event, second_site_event, circle_event,
                first_bisector, second_bisector);
            return std::make_pair(static_cast<Edge*>(result.first),
                                  static_cast<Edge*>(result.second));
          },
          py::arg("first_site_event"), py::arg("second_site_event"),
          py::arg("circle_event"), py::arg("first_bisector"),
          py::arg("second_bisector"))
      .def("_process_single_site", &Diagram::_process_single_site<coordinate_t>,
           py::arg("site"))
      .def("_reserve", &Diagram::_reserve, py::arg("sites_count"))
      .def_property_readonly("cells", &Diagram::cells)
      .def_property_readonly("edges", &Diagram::edges)
      .def_property_readonly("vertices", &Diagram::vertices);

  py::class_<Edge, std::unique_ptr<Edge, py::nodelete>>(m, EDGE_NAME)
      .def(py::init(
               [](Vertex* start, Cell* cell, bool is_linear, bool is_primary) {
                 Edge result{is_linear, is_primary};
                 result.vertex0(start);
                 result.cell(cell);
                 return result;
               }),
           py::arg("start"), py::arg("cell").none(false), py::arg("is_linear"),
           py::arg("is_primary"))
      .def("__repr__", to_repr<Edge>)
      .def_property_readonly("cell",
                             [](const Edge& self) { return self.cell(); })
      .def_property_readonly("end",
                             [](const Edge& self) {
                               return self.twin() == nullptr ? nullptr
                                                             : self.vertex1();
                             })
      .def_property_readonly("is_curved", &Edge::is_curved)
      .def_property_readonly("is_finite",
                             [](const Edge& self) {
                               return self.twin() == nullptr ? false
                                                             : self.is_finite();
                             })
      .def_property_readonly(
          "is_infinite",
          [](const Edge& self) {
            return self.twin() == nullptr ? true : self.is_infinite();
          })
      .def_property_readonly("is_linear", &Edge::is_linear)
      .def_property_readonly("is_primary", &Edge::is_primary)
      .def_property_readonly("is_secondary", &Edge::is_secondary)
      .def_property(
          "next", [](const Edge& self) { return self.next(); },
          [](Edge& self, Edge* value) { self.next(value); })
      .def_property(
          "prev", [](const Edge& self) { return self.prev(); },
          [](Edge& self, Edge* value) { self.prev(value); })
      .def_property_readonly("rot_next",
                             [](const Edge& self) {
                               return self.prev() == nullptr ? nullptr
                                                             : self.rot_next();
                             })
      .def_property_readonly("rot_prev",
                             [](const Edge& self) {
                               return self.prev() == nullptr ? nullptr
                                                             : self.rot_prev();
                             })
      .def_property_readonly("start",
                             [](const Edge& self) { return self.vertex0(); })
      .def_property(
          "twin", [](const Edge& self) { return self.twin(); },
          [](Edge& self, Edge* value) { self.twin(value); });

  py::class_<Point>(m, POINT_NAME)
      .def(py::init<coordinate_t, coordinate_t>(), py::arg("x"), py::arg("y"))
      .def(py::self == py::self)
      .def(
          "__lt__",
          [](const Point& self, const Point& other) {
            static const Predicates::point_comparison_predicate<Point>
                comparator;
            return comparator(self, other);
          },
          py::is_operator())
      .def("__repr__", to_repr<Point>)
      .def_property_readonly("x", [](const Point& self) { return self.x(); })
      .def_property_readonly("y", [](const Point& self) { return self.y(); });

  py::class_<RobustFloat>(m, ROBUST_FLOAT_NAME)
      .def(py::init<>())
      .def(py::init<double>(), py::arg("value"))
      .def(py::init<double, double>(), py::arg("value"),
           py::arg("relative_error"))
      .def(-py::self)
      .def(py::self + py::self)
      .def(py::self - py::self)
      .def(py::self * py::self)
      .def(py::self / py::self)
      .def(py::self += py::self)
      .def(py::self -= py::self)
      .def(py::self *= py::self)
      .def(py::self /= py::self)
      .def("__bool__",
           [](const RobustFloat& self) {
             return !boost::polygon::detail::is_zero(self);
           })
      .def("__repr__", to_repr<RobustFloat>)
      .def("sqrt", &RobustFloat::sqrt)
      .def_property_readonly("value", &RobustFloat::fpv)
      .def_property_readonly("relative_error", &RobustFloat::re);

  py::class_<RobustDifference>(m, ROBUST_DIFFERENCE_NAME)
      .def(py::init<>())
      .def(py::init<RobustFloat, RobustFloat>(), py::arg("minuend"),
           py::arg("subtrahend"))
      .def(-py::self)
      .def(py::self + RobustFloat())
      .def(py::self - RobustFloat())
      .def(py::self * RobustFloat())
      .def(py::self / RobustFloat())
      .def(py::self += RobustFloat())
      .def(py::self -= RobustFloat())
      .def(py::self *= RobustFloat())
      .def(py::self /= RobustFloat())
      .def(py::self + RobustDifference())
      .def(py::self - RobustDifference())
      .def(py::self * RobustDifference())
      .def(py::self += RobustDifference())
      .def(py::self -= RobustDifference())
      .def(py::self *= RobustDifference())
      .def("__repr__", to_repr<RobustDifference>)
      .def("evaluate", &RobustDifference::dif)
      .def_property_readonly("minuend", &RobustDifference::pos)
      .def_property_readonly("subtrahend", &RobustDifference::neg);

  py::class_<Segment>(m, SEGMENT_NAME)
      .def(py::init<Point, Point>(), py::arg("start"), py::arg("end"))
      .def("__repr__", to_repr<Segment>)
      .def(py::self == py::self)
      .def_readonly("start", &Segment::start)
      .def_readonly("end", &Segment::end);

  py::class_<SiteEvent>(m, SITE_EVENT_NAME)
      .def(py::init([](const Point& start, const Point& end,
                       std::size_t sorted_index, std::size_t initial_index,
                       bool is_inverse, SourceCategory source_category) {
             return (is_inverse ? SiteEvent{end, start}.inverse()
                                : SiteEvent{start, end})
                 .sorted_index(sorted_index)
                 .initial_index(initial_index)
                 .source_category(source_category);
           }),
           py::arg("start"), py::arg("end"), py::arg("sorted_index") = 0,
           py::arg("initial_index") = 0, py::arg("is_inverse") = false,
           py::arg("source_category") =
               SourceCategory::SOURCE_CATEGORY_SINGLE_POINT)
      .def(py::self == py::self)
      .def(
          "__lt__",
          [](const SiteEvent& self, const CircleEvent& other) {
            static const EventComparisonPredicate comparator;
            return comparator(self, other);
          },
          py::is_operator())
      .def(
          "__lt__",
          [](const SiteEvent& self, const SiteEvent& other) {
            static const EventComparisonPredicate comparator;
            return comparator(self, other);
          },
          py::is_operator())
      .def("__repr__", to_repr<SiteEvent>)
      .def_property_readonly(
          "comparison_point",
          [](const SiteEvent& self) {
            static const Predicates::node_comparison_predicate<BeachLineKey>
                comparator;
            return comparator.get_comparison_point(self);
          })
      .def("inverse", &SiteEvent::inverse)
      .def_property_readonly(
          "end", [](const SiteEvent& self) { return self.point1(); })
      .def_property_readonly(
          "initial_index",
          [](const SiteEvent& self) { return self.initial_index(); })
      .def_property_readonly("is_inverse", &SiteEvent::is_inverse)
      .def_property_readonly("is_point", &SiteEvent::is_point)
      .def_property_readonly("is_segment", &SiteEvent::is_segment)
      .def_property_readonly(
          "is_vertical",
          [](const SiteEvent& self) { return Predicates::is_vertical(self); })
      .def_property_readonly(
          "sorted_index",
          [](const SiteEvent& self) { return self.sorted_index(); })
      .def_property_readonly(
          "source_category",
          [](const SiteEvent& self) { return self.source_category(); })
      .def_property_readonly(
          "start", [](const SiteEvent& self) { return self.point0(); });

  py::class_<Vertex, std::unique_ptr<Vertex, py::nodelete>>(m, VERTEX_NAME)
      .def(py::init<double, double>(), py::arg("x"), py::arg("y"))
      .def("__repr__", to_repr<Vertex>)
      .def(py::self == py::self)
      .def_property(
          "incident_edge",
          [](const Vertex& self) { return self.incident_edge(); },
          [](Vertex& self, Edge* value) { self.incident_edge(value); })
      .def_property_readonly("is_degenerate", &Vertex::is_degenerate)
      .def_property_readonly("x", &Vertex::x)
      .def_property_readonly("y", &Vertex::y);

  m.def(
      "compare_floats",
      [](double left, double right, unsigned int max_ulps) {
        static const UlpComparator comparator;
        return comparator(left, right, max_ulps);
      },
      py::arg("left"), py::arg("right"), py::arg("max_ulps"));

  m.def(
      "compute_circle_event",
      [](CircleEvent& circle_event, const SiteEvent& first_site,
         const SiteEvent& second_site, const SiteEvent& third_site) {
        static Predicates::circle_formation_predicate<SiteEvent, CircleEvent>
            predicate;
        return predicate(first_site, second_site, third_site, circle_event);
      },
      py::arg("circle_event"), py::arg("first_site"), py::arg("second_site"),
      py::arg("third_site"));

  m.def(
      "compute_point_point_point_circle_event",
      [](CircleEvent& circle_event, const SiteEvent& first_site,
         const SiteEvent& second_site, const SiteEvent& third_site) {
        static Predicates::lazy_circle_formation_functor<SiteEvent, CircleEvent>
            functor;
        functor.ppp(first_site, second_site, third_site, circle_event);
      },
      py::arg("circle_event"), py::arg("first_site"), py::arg("second_site"),
      py::arg("third_site"));

  m.def(
      "compute_point_point_segment_circle_event",
      [](CircleEvent& circle_event, const SiteEvent& first_site,
         const SiteEvent& second_site, const SiteEvent& third_site,
         int segment_index) {
        static Predicates::lazy_circle_formation_functor<SiteEvent, CircleEvent>
            functor;
        functor.pps(first_site, second_site, third_site, segment_index,
                    circle_event);
      },
      py::arg("circle_event"), py::arg("first_site"), py::arg("second_site"),
      py::arg("third_site"), py::arg("segment_index"));

  m.def(
      "compute_point_segment_segment_circle_event",
      [](CircleEvent& circle_event, const SiteEvent& first_site,
         const SiteEvent& second_site, const SiteEvent& third_site,
         int point_index) {
        static Predicates::lazy_circle_formation_functor<SiteEvent, CircleEvent>
            functor;
        functor.pss(first_site, second_site, third_site, point_index,
                    circle_event);
      },
      py::arg("circle_event"), py::arg("first_site"), py::arg("second_site"),
      py::arg("third_site"), py::arg("point_index"));

  m.def(
      "compute_segment_segment_segment_circle_event",
      [](CircleEvent& circle_event, const SiteEvent& first_site,
         const SiteEvent& second_site, const SiteEvent& third_site) {
        static Predicates::lazy_circle_formation_functor<SiteEvent, CircleEvent>
            functor;
        functor.sss(first_site, second_site, third_site, circle_event);
      },
      py::arg("circle_event"), py::arg("first_site"), py::arg("second_site"),
      py::arg("third_site"));

  m.def(
      "distance_to_point_arc",
      [](const SiteEvent& site, const Point& point) {
        static const Predicates::distance_predicate<SiteEvent> comparator;
        return comparator.find_distance_to_point_arc(site, point);
      },
      py::arg("site"), py::arg("point"));

  m.def(
      "distance_to_segment_arc",
      [](const SiteEvent& site, const Point& point) {
        static const Predicates::distance_predicate<SiteEvent> comparator;
        return comparator.find_distance_to_segment_arc(site, point);
      },
      py::arg("site"), py::arg("point"));

  m.def(
      "horizontal_goes_through_right_arc_first",
      [](const SiteEvent& left_site, const SiteEvent& right_site,
         const Point& point) {
        static const Predicates::distance_predicate<SiteEvent> comparator;
        return comparator(left_site, right_site, point);
      },
      py::arg("left_site"), py::arg("right_site"), py::arg("point"));

  m.def(
      "point_point_horizontal_goes_through_right_arc_first",
      [](const SiteEvent& left_site, const SiteEvent& right_site,
         const Point& point) {
        static const Predicates::distance_predicate<SiteEvent> comparator;
        return comparator.pp(left_site, right_site, point);
      },
      py::arg("left_site"), py::arg("right_site"), py::arg("point"));

  m.def(
      "point_point_point_circle_exists",
      [](const SiteEvent& first_site, const SiteEvent& second_site,
         const SiteEvent& third_site) {
        static const Predicates::circle_existence_predicate<SiteEvent>
            predicate;
        return predicate.ppp(first_site, second_site, third_site);
      },
      py::arg("first_site"), py::arg("second_site"), py::arg("third_site"));

  m.def(
      "point_point_segment_circle_exists",
      [](const SiteEvent& first_site, const SiteEvent& second_site,
         const SiteEvent& third_site, int segment_index) {
        static const Predicates::circle_existence_predicate<SiteEvent>
            predicate;
        return predicate.pps(first_site, second_site, third_site,
                             segment_index);
      },
      py::arg("first_site"), py::arg("second_site"), py::arg("third_site"),
      py::arg("segment_index"));

  m.def(
      "point_segment_horizontal_goes_through_right_arc_first",
      [](const SiteEvent& left_site, const SiteEvent& right_site,
         const Point& point, bool reverse_order) {
        static const Predicates::distance_predicate<SiteEvent> comparator;
        return comparator.ps(left_site, right_site, point, reverse_order);
      },
      py::arg("left_site"), py::arg("right_site"), py::arg("point"),
      py::arg("reverse_order"));

  m.def(
      "point_segment_segment_circle_exists",
      [](const SiteEvent& first_site, const SiteEvent& second_site,
         const SiteEvent& third_site, int point_index) {
        static const Predicates::circle_existence_predicate<SiteEvent>
            predicate;
        return predicate.pss(first_site, second_site, third_site, point_index);
      },
      py::arg("first_site"), py::arg("second_site"), py::arg("third_site"),
      py::arg("point_index"));

  m.def(
      "recompute_point_point_point_circle_event",
      [](CircleEvent& circle_event, const SiteEvent& first_site,
         const SiteEvent& second_site, const SiteEvent& third_site,
         bool recompute_center_x, bool recompute_center_y,
         bool recompute_lower_x) {
        static Predicates::mp_circle_formation_functor<SiteEvent, CircleEvent>
            functor;
        functor.ppp(first_site, second_site, third_site, circle_event,
                    recompute_center_x, recompute_center_y, recompute_lower_x);
      },
      py::arg("circle_event"), py::arg("first_site"), py::arg("second_site"),
      py::arg("third_site"), py::arg("recompute_center_x") = true,
      py::arg("recompute_center_y") = true,
      py::arg("recompute_lower_x") = true);

  m.def(
      "recompute_point_point_segment_circle_event",
      [](CircleEvent& circle_event, const SiteEvent& first_site,
         const SiteEvent& second_site, const SiteEvent& third_site,
         int segment_index, bool recompute_center_x, bool recompute_center_y,
         bool recompute_lower_x) {
        static Predicates::mp_circle_formation_functor<SiteEvent, CircleEvent>
            functor;
        functor.pps(first_site, second_site, third_site, segment_index,
                    circle_event, recompute_center_x, recompute_center_y,
                    recompute_lower_x);
      },
      py::arg("circle_event"), py::arg("first_site"), py::arg("second_site"),
      py::arg("third_site"), py::arg("segment_index"),
      py::arg("recompute_center_x") = true,
      py::arg("recompute_center_y") = true,
      py::arg("recompute_lower_x") = true);

  m.def(
      "recompute_point_segment_segment_circle_event",
      [](CircleEvent& circle_event, const SiteEvent& first_site,
         const SiteEvent& second_site, const SiteEvent& third_site,
         int point_index, bool recompute_center_x, bool recompute_center_y,
         bool recompute_lower_x) {
        static Predicates::mp_circle_formation_functor<SiteEvent, CircleEvent>
            functor;
        functor.pss(first_site, second_site, third_site, point_index,
                    circle_event, recompute_center_x, recompute_center_y,
                    recompute_lower_x);
      },
      py::arg("circle_event"), py::arg("first_site"), py::arg("second_site"),
      py::arg("third_site"), py::arg("point_index"),
      py::arg("recompute_center_x") = true,
      py::arg("recompute_center_y") = true,
      py::arg("recompute_lower_x") = true);

  m.def(
      "recompute_segment_segment_segment_circle_event",
      [](CircleEvent& circle_event, const SiteEvent& first_site,
         const SiteEvent& second_site, const SiteEvent& third_site,
         bool recompute_center_x, bool recompute_center_y,
         bool recompute_lower_x) {
        static Predicates::mp_circle_formation_functor<SiteEvent, CircleEvent>
            functor;
        functor.sss(first_site, second_site, third_site, circle_event,
                    recompute_center_x, recompute_center_y, recompute_lower_x);
      },
      py::arg("circle_event"), py::arg("first_site"), py::arg("second_site"),
      py::arg("third_site"), py::arg("recompute_center_x") = true,
      py::arg("recompute_center_y") = true,
      py::arg("recompute_lower_x") = true);

  m.def("robust_cross_product", &Predicates::robust_cross_product,
        py::arg("first_dx"), py::arg("first_dy"), py::arg("second_dx"),
        py::arg("second_dy"));

  m.def(
      "robust_product_with_sqrt",
      [](BigInt& left, BigInt& right) {
        RobustSumExpression expression;
        return expression.eval1(&left, &right);
      },
      py::arg("left"), py::arg("right"));

  m.def(
      "robust_sum_of_products_with_sqrt_pairs",
      [](std::array<BigInt, 2>& left, std::array<BigInt, 2>& right) {
        RobustSumExpression expression;
        return expression.eval2(left.data(), right.data());
      },
      py::arg("left"), py::arg("right"));

  m.def(
      "robust_sum_of_products_with_sqrt_triplets",
      [](std::array<BigInt, 3>& left, std::array<BigInt, 3>& right) {
        RobustSumExpression expression;
        return expression.eval3(left.data(), right.data());
      },
      py::arg("left"), py::arg("right"));

  m.def(
      "robust_sum_of_products_with_sqrt_quadruplets",
      [](std::array<BigInt, 4>& left, std::array<BigInt, 4>& right) {
        RobustSumExpression expression;
        return expression.eval4(left.data(), right.data());
      },
      py::arg("left"), py::arg("right"));

  m.def(
      "segment_segment_horizontal_goes_through_right_arc_first",
      [](const SiteEvent& left_site, const SiteEvent& right_site,
         const Point& point) {
        static const Predicates::distance_predicate<SiteEvent> comparator;
        return comparator.ss(left_site, right_site, point);
      },
      py::arg("left_site"), py::arg("right_site"), py::arg("point"));

  m.def(
      "segment_segment_segment_circle_exists",
      [](const SiteEvent& first_site, const SiteEvent& second_site,
         const SiteEvent& third_site) {
        static const Predicates::circle_existence_predicate<SiteEvent>
            predicate;
        return predicate.sss(first_site, second_site, third_site);
      },
      py::arg("first_site"), py::arg("second_site"), py::arg("third_site"));

  m.def("to_first_point_segment_segment_quadruplets_expression",
        [](std::array<BigInt, 4> left, std::array<BigInt, 4> right) {
          static Predicates::mp_circle_formation_functor<SiteEvent, CircleEvent>
              functor;
          return functor.sqrt_expr_evaluator_pss3<BigInt, BigFloat>(
              left.data(), right.data());
        });

  m.def(
      "to_orientation",
      [](const Point& vertex, const Point& first_ray_point,
         const Point& second_ray_point) {
        return Predicates::ot::eval(vertex, first_ray_point, second_ray_point);
      },
      py::arg("vertex"), py::arg("first_ray_point"),
      py::arg("second_ray_point"));

  m.def("to_second_point_segment_segment_quadruplets_expression",
        [](std::array<BigInt, 4> left, std::array<BigInt, 4> right) {
          static Predicates::mp_circle_formation_functor<SiteEvent, CircleEvent>
              functor;
          return functor.sqrt_expr_evaluator_pss4<BigInt, BigFloat>(
              left.data(), right.data());
        });
}


//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
#include "smtk/common/UUIDGenerator.h"

#ifndef _MSC_VER
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored"-Wshadow"
#endif
#include <boost/uuid/uuid_generators.hpp>
#ifndef _MSC_VER
#  pragma GCC diagnostic pop
#endif

#include <ctime> // for time()
#include <stdlib.h> // for getenv()/_dupenv_s()

namespace
{
// Return true when \a vname exists in the environment (empty or not).
static bool checkenv(const char* vname)
{
#if !defined(_WIN32) || defined(__CYGWIN__)
  return getenv(vname) ? true : false;
#else
  char* buf; //allocated or assigned by _dupenv_s
  const bool valid = (_dupenv_s(&buf, NULL, vname) == 0) && (buf != NULL);
  free(buf); //perfectly valid to free a NULL pointer
  return valid;
#endif
}

}

namespace smtk {
  namespace common {

class UUIDGenerator::Internal
{
public:
  Internal()
  {
    if (checkenv("SMTK_IN_VALGRIND"))
      {
      // This is a poor technique for seeding or
      // we would initialize this way all the time.
      this->m_mtseed.seed(
        static_cast<boost::mt19937::result_type>(
          time(NULL)));
      this->m_randomGenerator =
        new boost::uuids::basic_random_generator<boost::mt19937>(&this->m_mtseed);
      }
    else
      {
      this->m_randomGenerator =
        new boost::uuids::basic_random_generator<boost::mt19937>;
      }
  }
  ~Internal()
  {
    delete this->m_randomGenerator;
  }

  boost::mt19937 m_mtseed;
  boost::uuids::basic_random_generator<boost::mt19937>* m_randomGenerator;
  boost::uuids::nil_generator m_nullGenerator;
};

UUIDGenerator::UUIDGenerator()
{
  this->P = new Internal;
}

UUIDGenerator::~UUIDGenerator()
{
  delete this->P;
}

UUID UUIDGenerator::random()
{
  return UUID((*this->P->m_randomGenerator)());
}

/// Generate a nil UUID.
UUID UUIDGenerator::null()
{
  return UUID(this->P->m_nullGenerator());
}

  } // namespace common
} // namespace smtk


/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_EXAMPLE_CPPCON_2014_MATRIX_RING_HPP
#define BOOST_HANA_EXAMPLE_CPPCON_2014_MATRIX_RING_HPP

#include "matrix.hpp"

#include <boost/hana/detail/std/integral_constant.hpp>
#include <boost/hana/foldable.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/sequence.hpp>
#include <boost/hana/range.hpp>
#include <boost/hana/ring.hpp>
#include <boost/hana/tuple.hpp>

#include <utility>

namespace boost { namespace hana {
    template <unsigned R1, unsigned C1, unsigned R2, unsigned C2>
    struct mult_impl<cppcon::Matrix<R1, C1>, cppcon::Matrix<R2, C2>> {
        template <typename M1, typename M2>
        static constexpr decltype(auto) apply(M1&& m1, M2&& m2) {
            static_assert(C1 == R2,
                "wrong dimensions for matrix multiplication");
            auto cols = cppcon::columns(std::forward<M2>(m2));
            return unpack(
                transform(cppcon::rows(std::forward<M1>(m1)),
                    [&](auto&& row) -> decltype(auto) {
                        return zip.with(cppcon::detail::tuple_scalar_product,
                            repeat<Tuple>(uint<R1>, std::forward<decltype(row)>(row)),
                            cols
                        );
                    }
                ),
                cppcon::matrix
            );
        }
    };

    template <unsigned R, unsigned C>
    struct one_impl<cppcon::Matrix<R, C>> {
        static constexpr decltype(auto) apply() {
            return unpack(range_c<unsigned, 0, R>, [](auto ...n) {
                return unpack(range_c<unsigned, 0, C>, [=](auto ...m) {
                    auto row = [=](auto n) {
                        return cppcon::row(if_(n == m, int_<1>, int_<0>)...);
                    };
                    return cppcon::matrix(row(n)...);
                });
            });
        }
    };
}}

#endif // !BOOST_HANA_EXAMPLE_CPPCON_2014_MATRIX_RING_HPP


/*
 * Copyright (c) 2020 Johannes Pankert <pankertj@ethz.ch>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of this work 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 <assert.h>
#include <ocs2_core/automatic_differentiation/CppAdInterface.h>
#include <perceptive_mpc/costs/PointsOnRobot.h>

#include <Eigen/Dense>
#include <perceptive_mpc/kinematics/KinematicsInterface.hpp>

using namespace ocs2;
using namespace perceptive_mpc;

PointsOnRobot::PointsOnRobot(const PointsOnRobot& rhs)
    : points_(rhs.points_),
      radii_(rhs.radii_),
      cppAdInterface_(new ocs2::CppAdInterface<double>(*rhs.cppAdInterface_)),
      kinematics_(rhs.kinematics_) {}

PointsOnRobot::PointsOnRobot(const PointsOnRobotConfig& config) : points_(), radii_(), kinematics_(config.kinematics) {
  const auto& pointsAndRadii = config.pointsAndRadii;

  int numPoints = 0;
  for (const auto& segments : pointsAndRadii) {
    std::vector<double> pointsOnSegment;
    for (const auto& pointRadius : segments) {
      pointsOnSegment.push_back(pointRadius.first);
      numPoints++;
    }
    points_.push_back(pointsOnSegment);
  }
  int idx = 0;
  radii_ = Eigen::VectorXd(numPoints);
  for (const auto& segments : pointsAndRadii) {
    for (const auto& pointRadius : segments) {
      radii_(idx++) = pointRadius.second;
    }
  }
}
void PointsOnRobot::initialize(const std::string& modelName, const std::string& modelFolder, bool recompileLibraries, bool verbose) {
  setADInterfaces(modelName, modelFolder);
  if (recompileLibraries) {
    createModels(verbose);
  } else {
    loadModelsIfAvailable(verbose);
  }
}
Eigen::VectorXd PointsOnRobot::getPoints(const Eigen::VectorXd& state) const {
  return cppAdInterface_->getFunctionValue(state);
}
Eigen::MatrixXd PointsOnRobot::getJacobian(const Eigen::VectorXd& state) const {
  return cppAdInterface_->getJacobian(state);
}
visualization_msgs::MarkerArray PointsOnRobot::getVisualization(const Eigen::VectorXd& state) const {
  visualization_msgs::MarkerArray markerArray;
  markerArray.markers.resize(radii_.size());

  Eigen::VectorXd points = getPoints(state);

  for (int i = 0; i < markerArray.markers.size(); i++) {
    auto& marker = markerArray.markers[i];
    marker.type = visualization_msgs::Marker::Type::SPHERE;
    marker.id = i;
    marker.action = 0;
    marker.scale.x = radii_[i] * 2;
    marker.scale.y = radii_[i] * 2;
    marker.scale.z = radii_[i] * 2;
    marker.pose.position.x = points(3 * i + 0);
    marker.pose.position.y = points(3 * i + 1);
    marker.pose.position.z = points(3 * i + 2);

    marker.pose.orientation.w = 1;
    marker.pose.orientation.x = 0;
    marker.pose.orientation.y = 0;
    marker.pose.orientation.z = 0;

    marker.color.a = 0.5;
    marker.color.r = 0.5;
    marker.color.b = 0.0;
    marker.color.g = 0.0;

    marker.frame_locked = true;
    marker.header.frame_id = "odom";
    marker.header.stamp = ros::Time::now();
  }
  return markerArray;
}

void PointsOnRobot::setADInterfaces(const std::string& modelName, const std::string& modelFolder) {
  using ad_interface = CppAdInterface<double>;
  using ad_dynamic_vector_t = ad_interface::ad_dynamic_vector_t;
  using ad_scalar_t = ad_interface::ad_scalar_t;

  int numPoints = 0;
  for (int i = 0; i < points_.size(); i++) {
    numPoints += points_[i].size();
  }
  assert(numPoints == radii_.size());

  auto state2MultiplePointsAd = [&, this](const ad_dynamic_vector_t& x, ad_dynamic_vector_t& y) {
    Eigen::Matrix<ad_scalar_t, 3, -1> matrixResult = kinematics_->computeState2MultiplePointsOnRobot(x, points_);
    y = Eigen::Map<Eigen::Matrix<ad_scalar_t, -1, 1>>(matrixResult.data(), matrixResult.size());
  };
  cppAdInterface_.reset(
      new CppAdInterface<double>(state2MultiplePointsAd, numPoints * 3, Definitions::STATE_DIM_, modelName + "_intermediate", modelFolder));
}

void PointsOnRobot::createModels(bool verbose) {
  cppAdInterface_->createModels(CppAdInterface<double>::ApproximationOrder::First, verbose);
}

void PointsOnRobot::loadModelsIfAvailable(bool verbose) {
  cppAdInterface_->loadModelsIfAvailable(CppAdInterface<double>::ApproximationOrder::First, verbose);
}
Eigen::VectorXd PointsOnRobot::getRadii() const {
  return radii_;
}
int PointsOnRobot::numOfPoints() const {
  return radii_.size();
}


/**
 * @author Tiago de Freitas Pereira <tiago.pereira@idiap.ch>
 * @date Sun 01 Fev 09:40:00 2015
 *
 * @brief Python API for bob::learn::em
 *
 * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 */

#include "main.h"
#include <boost/make_shared.hpp>

/******************************************************************/
/************ Constructor Section *********************************/
/******************************************************************/

static int extract_GMMStats_1d(PyObject *list,
                             std::vector<boost::shared_ptr<bob::learn::em::GMMStats> >& training_data)
{
  for (int i=0; i<PyList_GET_SIZE(list); i++){

    PyBobLearnEMGMMStatsObject* stats;
    if (!PyArg_Parse(PyList_GetItem(list, i), "O!", &PyBobLearnEMGMMStats_Type, &stats)){
      PyErr_Format(PyExc_RuntimeError, "Expected GMMStats objects");
      return -1;
    }
    training_data.push_back(stats->cxx);
  }
  return 0;
}

static int extract_GMMStats_2d(PyObject *list,
                             std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > >& training_data)
{
  for (int i=0; i<PyList_GET_SIZE(list); i++)
  {
    PyObject* another_list;
    if(!PyArg_Parse(PyList_GetItem(list, i), "O!", &PyList_Type, &another_list)){
      PyErr_Format(PyExc_RuntimeError, "Expected a list of lists of GMMStats objects [[GMMStats,GMMStats],[GMMStats,GMMStats].....[GMMStats,GMMStats]]");
      return -1;
    }

    std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > another_training_data;
    for (int j=0; j<PyList_GET_SIZE(another_list); j++){

      PyBobLearnEMGMMStatsObject* stats;
      if (!PyArg_Parse(PyList_GetItem(another_list, j), "O!", &PyBobLearnEMGMMStats_Type, &stats)){
        PyErr_Format(PyExc_RuntimeError, "Expected GMMStats objects");
        return -1;
      }
      another_training_data.push_back(stats->cxx);
    }
    training_data.push_back(another_training_data);
  }
  return 0;
}

template <int N>
static PyObject* vector_as_list(const std::vector<blitz::Array<double,N> >& vec)
{
  PyObject* list = PyList_New(vec.size());
  for(size_t i=0; i<vec.size(); i++){
    blitz::Array<double,N> numpy_array = vec[i];
    PyObject* numpy_py_object = PyBlitzArrayCxx_AsNumpy(numpy_array);
    PyList_SET_ITEM(list, i, numpy_py_object);
  }
  return list;
}

template <int N>
int list_as_vector(PyObject* list, std::vector<blitz::Array<double,N> >& vec)
{
  for (int i=0; i<PyList_GET_SIZE(list); i++)
  {
    PyBlitzArrayObject* blitz_object;
    if (!PyArg_Parse(PyList_GetItem(list, i), "O&", &PyBlitzArray_Converter, &blitz_object)){
      PyErr_Format(PyExc_RuntimeError, "Expected numpy array object");
      return -1;
    }
    auto blitz_object_ = make_safe(blitz_object);
    vec.push_back(*PyBlitzArrayCxx_AsBlitz<double,N>(blitz_object));
  }
  return 0;
}

static auto JFATrainer_doc = bob::extension::ClassDoc(
  BOB_EXT_MODULE_PREFIX ".JFATrainer",
  "Trains a Joint Factor Analysis (:ref:`JFA <jfa>`) on top of GMMs"
  "",
  ""
).add_constructor(
  bob::extension::FunctionDoc(
    "__init__",
    "Constructor. Builds a new JFATrainer",
    "",
    true
  )
  .add_prototype("other","")
  .add_prototype("","")
  .add_parameter("other", ":py:class:`bob.learn.em.JFATrainer`", "A JFATrainer object to be copied.")
);

static int PyBobLearnEMJFATrainer_init_copy(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {

  char** kwlist = JFATrainer_doc.kwlist(0);
  PyBobLearnEMJFATrainerObject* o;
  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", kwlist, &PyBobLearnEMJFATrainer_Type, &o)){
    JFATrainer_doc.print_usage();
    return -1;
  }

  self->cxx.reset(new bob::learn::em::JFATrainer(*o->cxx));
  return 0;
}

static int PyBobLearnEMJFATrainer_init(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  // get the number of command line arguments
  int nargs = (args?PyTuple_Size(args):0) + (kwargs?PyDict_Size(kwargs):0);

  switch(nargs){
    case 0:{
      self->cxx.reset(new bob::learn::em::JFATrainer());
      return 0;
    }
    case 1:{
      // If the constructor input is JFATrainer object
      return PyBobLearnEMJFATrainer_init_copy(self, args, kwargs);
    }
    default:{
      PyErr_Format(PyExc_RuntimeError, "number of arguments mismatch - %s requires only 0 and 1 argument, but you provided %d (see help)", Py_TYPE(self)->tp_name, nargs);
      JFATrainer_doc.print_usage();
      return -1;
    }
  }
  BOB_CATCH_MEMBER("cannot create JFATrainer", -1)
  return 0;
}

static void PyBobLearnEMJFATrainer_delete(PyBobLearnEMJFATrainerObject* self) {
  self->cxx.reset();
  Py_TYPE(self)->tp_free((PyObject*)self);
}

int PyBobLearnEMJFATrainer_Check(PyObject* o) {
  return PyObject_IsInstance(o, reinterpret_cast<PyObject*>(&PyBobLearnEMJFATrainer_Type));
}

static PyObject* PyBobLearnEMJFATrainer_RichCompare(PyBobLearnEMJFATrainerObject* self, PyObject* other, int op) {
  BOB_TRY

  if (!PyBobLearnEMJFATrainer_Check(other)) {
    PyErr_Format(PyExc_TypeError, "cannot compare `%s' with `%s'", Py_TYPE(self)->tp_name, Py_TYPE(other)->tp_name);
    return 0;
  }
  auto other_ = reinterpret_cast<PyBobLearnEMJFATrainerObject*>(other);
  switch (op) {
    case Py_EQ:
      if (*self->cxx==*other_->cxx) Py_RETURN_TRUE; else Py_RETURN_FALSE;
    case Py_NE:
      if (*self->cxx==*other_->cxx) Py_RETURN_FALSE; else Py_RETURN_TRUE;
    default:
      Py_INCREF(Py_NotImplemented);
      return Py_NotImplemented;
  }
  BOB_CATCH_MEMBER("cannot compare JFATrainer objects", 0)
}

/******************************************************************/
/************ Variables Section ***********************************/
/******************************************************************/

static auto acc_v_a1 = bob::extension::VariableDoc(
  "acc_v_a1",
  "array_like <float, 3D>",
  "Accumulator updated during the E-step",
  ""
);
PyObject* PyBobLearnEMJFATrainer_get_acc_v_a1(PyBobLearnEMJFATrainerObject* self, void*){
  BOB_TRY
  return PyBlitzArrayCxx_AsConstNumpy(self->cxx->getAccVA1());
  BOB_CATCH_MEMBER("acc_v_a1 could not be read", 0)
}
int PyBobLearnEMJFATrainer_set_acc_v_a1(PyBobLearnEMJFATrainerObject* self, PyObject* value, void*){
  BOB_TRY
  PyBlitzArrayObject* input;
  if (!PyBlitzArray_Converter(value, &input)){
    PyErr_Format(PyExc_RuntimeError, "%s %s expects a 3D array of floats", Py_TYPE(self)->tp_name, acc_v_a1.name());
    return -1;
  }
  auto o_ = make_safe(input);

  // perform check on the input
  if (input->type_num != NPY_FLOAT64){
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for input array `%s`", Py_TYPE(self)->tp_name, acc_v_a1.name());
    return -1;
  }

  if (input->ndim != 3){
    PyErr_Format(PyExc_TypeError, "`%s' only processes 3D arrays of float64 for `%s`", Py_TYPE(self)->tp_name, acc_v_a1.name());
    return -1;
  }

  if (input->shape[0] != (Py_ssize_t)self->cxx->getAccVA1().extent(0) && input->shape[1] != (Py_ssize_t)self->cxx->getAccVA1().extent(1) && input->shape[2] != (Py_ssize_t)self->cxx->getAccVA1().extent(2)) {
    PyErr_Format(PyExc_TypeError, "`%s' 3D `input` array should have the shape [%" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d] not [%" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d] for `%s`", Py_TYPE(self)->tp_name, (Py_ssize_t)self->cxx->getAccVA1().extent(0), (Py_ssize_t)self->cxx->getAccVA1().extent(1), (Py_ssize_t)self->cxx->getAccVA1().extent(2), (Py_ssize_t)input->shape[0], (Py_ssize_t)input->shape[1], (Py_ssize_t)input->shape[2], acc_v_a1.name());
    return -1;
  }

  auto b = PyBlitzArrayCxx_AsBlitz<double,3>(input, "acc_v_a1");
  if (!b) return -1;
  self->cxx->setAccVA1(*b);
  return 0;
  BOB_CATCH_MEMBER("acc_v_a1 could not be set", -1)
}

static auto acc_v_a2 = bob::extension::VariableDoc(
  "acc_v_a2",
  "array_like <float, 2D>",
  "Accumulator updated during the E-step",
  ""
);
PyObject* PyBobLearnEMJFATrainer_get_acc_v_a2(PyBobLearnEMJFATrainerObject* self, void*){
  BOB_TRY
  return PyBlitzArrayCxx_AsConstNumpy(self->cxx->getAccVA2());
  BOB_CATCH_MEMBER("acc_v_a2 could not be read", 0)
}
int PyBobLearnEMJFATrainer_set_acc_v_a2(PyBobLearnEMJFATrainerObject* self, PyObject* value, void*){
  BOB_TRY
  PyBlitzArrayObject* input;
  if (!PyBlitzArray_Converter(value, &input)){
    PyErr_Format(PyExc_RuntimeError, "%s %s expects a 2D array of floats", Py_TYPE(self)->tp_name, acc_v_a2.name());
    return -1;
  }
  auto o_ = make_safe(input);

  // perform check on the input
  if (input->type_num != NPY_FLOAT64){
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for input array `%s`", Py_TYPE(self)->tp_name, acc_v_a2.name());
    return -1;
  }

  if (input->ndim != 2){
    PyErr_Format(PyExc_TypeError, "`%s' only processes 2D arrays of float64 for `%s`", Py_TYPE(self)->tp_name, acc_v_a2.name());
    return -1;
  }

  if (input->shape[0] != (Py_ssize_t)self->cxx->getAccVA2().extent(0) && input->shape[1] != (Py_ssize_t)self->cxx->getAccVA2().extent(1)) {
    PyErr_Format(PyExc_TypeError, "`%s' 2D `input` array should have the shape [%" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d] not [%" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d] for `%s`", Py_TYPE(self)->tp_name, (Py_ssize_t)self->cxx->getAccVA2().extent(0), (Py_ssize_t)self->cxx->getAccVA2().extent(1), input->shape[0], input->shape[1], acc_v_a2.name());
    return -1;
  }

  auto b = PyBlitzArrayCxx_AsBlitz<double,2>(input, "acc_v_a2");
  if (!b) return -1;
  self->cxx->setAccVA2(*b);
  return 0;
  BOB_CATCH_MEMBER("acc_v_a2 could not be set", -1)
}

static auto acc_u_a1 = bob::extension::VariableDoc(
  "acc_u_a1",
  "array_like <float, 3D>",
  "Accumulator updated during the E-step",
  ""
);
PyObject* PyBobLearnEMJFATrainer_get_acc_u_a1(PyBobLearnEMJFATrainerObject* self, void*){
  BOB_TRY
  return PyBlitzArrayCxx_AsConstNumpy(self->cxx->getAccUA1());
  BOB_CATCH_MEMBER("acc_u_a1 could not be read", 0)
}
int PyBobLearnEMJFATrainer_set_acc_u_a1(PyBobLearnEMJFATrainerObject* self, PyObject* value, void*){
  BOB_TRY
  PyBlitzArrayObject* input;
  if (!PyBlitzArray_Converter(value, &input)){
    PyErr_Format(PyExc_RuntimeError, "%s %s expects a 3D array of floats", Py_TYPE(self)->tp_name, acc_u_a1.name());
    return -1;
  }
  auto o_ = make_safe(input);

  // perform check on the input
  if (input->type_num != NPY_FLOAT64){
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for input array `%s`", Py_TYPE(self)->tp_name, acc_u_a1.name());
    return -1;
  }

  if (input->ndim != 3){
    PyErr_Format(PyExc_TypeError, "`%s' only processes 3D arrays of float64 for `%s`", Py_TYPE(self)->tp_name, acc_u_a1.name());
    return -1;
  }

  if (input->shape[0] != (Py_ssize_t)self->cxx->getAccUA1().extent(0) && input->shape[1] != (Py_ssize_t)self->cxx->getAccUA1().extent(1) && input->shape[2] != (Py_ssize_t)self->cxx->getAccUA1().extent(2)) {
    PyErr_Format(PyExc_TypeError, "`%s' 3D `input` array should have the shape [%" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d] not [%" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d] for `%s`", Py_TYPE(self)->tp_name, (Py_ssize_t)self->cxx->getAccUA1().extent(0), (Py_ssize_t)self->cxx->getAccUA1().extent(1), (Py_ssize_t)self->cxx->getAccUA1().extent(2), (Py_ssize_t)input->shape[0], (Py_ssize_t)input->shape[1], (Py_ssize_t)input->shape[2], acc_u_a1.name());
    return -1;
  }

  auto b = PyBlitzArrayCxx_AsBlitz<double,3>(input, "acc_u_a1");
  if (!b) return -1;
  self->cxx->setAccUA1(*b);
  return 0;
  BOB_CATCH_MEMBER("acc_u_a1 could not be set", -1)
}

static auto acc_u_a2 = bob::extension::VariableDoc(
  "acc_u_a2",
  "array_like <float, 2D>",
  "Accumulator updated during the E-step",
  ""
);
PyObject* PyBobLearnEMJFATrainer_get_acc_u_a2(PyBobLearnEMJFATrainerObject* self, void*){
  BOB_TRY
  return PyBlitzArrayCxx_AsConstNumpy(self->cxx->getAccUA2());
  BOB_CATCH_MEMBER("acc_u_a2 could not be read", 0)
}
int PyBobLearnEMJFATrainer_set_acc_u_a2(PyBobLearnEMJFATrainerObject* self, PyObject* value, void*){
  BOB_TRY
  PyBlitzArrayObject* input;
  if (!PyBlitzArray_Converter(value, &input)){
    PyErr_Format(PyExc_RuntimeError, "%s %s expects a 2D array of floats", Py_TYPE(self)->tp_name, acc_u_a2.name());
    return -1;
  }
  auto o_ = make_safe(input);

  // perform check on the input
  if (input->type_num != NPY_FLOAT64){
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for input array `%s`", Py_TYPE(self)->tp_name, acc_u_a2.name());
    return -1;
  }

  if (input->ndim != 2){
    PyErr_Format(PyExc_TypeError, "`%s' only processes 2D arrays of float64 for `%s`", Py_TYPE(self)->tp_name, acc_u_a2.name());
    return -1;
  }

  if (input->shape[0] != (Py_ssize_t)self->cxx->getAccUA2().extent(0) && input->shape[1] != (Py_ssize_t)self->cxx->getAccUA2().extent(1)) {
    PyErr_Format(PyExc_TypeError, "`%s' 3D `input` array should have the shape [%" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d] not [%" PY_FORMAT_SIZE_T "d, %" PY_FORMAT_SIZE_T "d] for `%s`", Py_TYPE(self)->tp_name, (Py_ssize_t)self->cxx->getAccUA2().extent(0), (Py_ssize_t)self->cxx->getAccUA2().extent(1), input->shape[0], input->shape[1], acc_u_a2.name());
    return -1;
  }

  auto b = PyBlitzArrayCxx_AsBlitz<double,2>(input, "acc_u_a2");
  if (!b) return -1;
  self->cxx->setAccUA2(*b);
  return 0;
  BOB_CATCH_MEMBER("acc_u_a2 could not be set", -1)
}

static auto acc_d_a1 = bob::extension::VariableDoc(
  "acc_d_a1",
  "array_like <float, 1D>",
  "Accumulator updated during the E-step",
  ""
);
PyObject* PyBobLearnEMJFATrainer_get_acc_d_a1(PyBobLearnEMJFATrainerObject* self, void*){
  BOB_TRY
  return PyBlitzArrayCxx_AsConstNumpy(self->cxx->getAccDA1());
  BOB_CATCH_MEMBER("acc_d_a1 could not be read", 0)
}
int PyBobLearnEMJFATrainer_set_acc_d_a1(PyBobLearnEMJFATrainerObject* self, PyObject* value, void*){
  BOB_TRY
  PyBlitzArrayObject* input;
  if (!PyBlitzArray_Converter(value, &input)){
    PyErr_Format(PyExc_RuntimeError, "%s %s expects a 1D array of floats", Py_TYPE(self)->tp_name, acc_d_a1.name());
    return -1;
  }
  auto o_ = make_safe(input);

  // perform check on the input
  if (input->type_num != NPY_FLOAT64){
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for input array `%s`", Py_TYPE(self)->tp_name, acc_d_a1.name());
    return -1;
  }

  if (input->ndim != 1){
    PyErr_Format(PyExc_TypeError, "`%s' only processes 1D arrays of float64 for `%s`", Py_TYPE(self)->tp_name, acc_d_a1.name());
    return -1;
  }

  if (input->shape[0] != (Py_ssize_t)self->cxx->getAccDA1().extent(0)) {
    PyErr_Format(PyExc_TypeError, "`%s' 1D `input` array should have %" PY_FORMAT_SIZE_T "d elements, not %" PY_FORMAT_SIZE_T "d for `%s`", Py_TYPE(self)->tp_name, (Py_ssize_t)self->cxx->getAccDA1().extent(0), input->shape[0], acc_d_a1.name());
    return -1;
  }

  auto b = PyBlitzArrayCxx_AsBlitz<double,1>(input, "acc_d_a1");
  if (!b) return -1;
  self->cxx->setAccDA1(*b);
  return 0;
  BOB_CATCH_MEMBER("acc_d_a1 could not be set", -1)
}

static auto acc_d_a2 = bob::extension::VariableDoc(
  "acc_d_a2",
  "array_like <float, 1D>",
  "Accumulator updated during the E-step",
  ""
);
PyObject* PyBobLearnEMJFATrainer_get_acc_d_a2(PyBobLearnEMJFATrainerObject* self, void*){
  BOB_TRY
  return PyBlitzArrayCxx_AsConstNumpy(self->cxx->getAccDA2());
  BOB_CATCH_MEMBER("acc_d_a2 could not be read", 0)
}
int PyBobLearnEMJFATrainer_set_acc_d_a2(PyBobLearnEMJFATrainerObject* self, PyObject* value, void*){
  BOB_TRY
  PyBlitzArrayObject* input;
  if (!PyBlitzArray_Converter(value, &input)){
    PyErr_Format(PyExc_RuntimeError, "%s %s expects a 1D array of floats", Py_TYPE(self)->tp_name, acc_d_a1.name());
    return -1;
  }
  auto o_ = make_safe(input);

  // perform check on the input
  if (input->type_num != NPY_FLOAT64){
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for input array `%s`", Py_TYPE(self)->tp_name, acc_d_a2.name());
    return -1;
  }

  if (input->ndim != 1){
    PyErr_Format(PyExc_TypeError, "`%s' only processes 1D arrays of float64 for `%s`", Py_TYPE(self)->tp_name, acc_d_a2.name());
    return -1;
  }

  if (input->shape[0] != (Py_ssize_t)self->cxx->getAccDA2().extent(0)) {
    PyErr_Format(PyExc_TypeError, "`%s' 1D `input` array should have %" PY_FORMAT_SIZE_T "d elements, not %" PY_FORMAT_SIZE_T "d for `%s`", Py_TYPE(self)->tp_name, (Py_ssize_t)self->cxx->getAccDA2().extent(0), input->shape[0], acc_d_a2.name());
    return -1;
  }

  auto b = PyBlitzArrayCxx_AsBlitz<double,1>(input, "acc_d_a2");
  if (!b) return -1;
  self->cxx->setAccDA2(*b);
  return 0;
  BOB_CATCH_MEMBER("acc_d_a2 could not be set", -1)
}

static auto __X__ = bob::extension::VariableDoc(
  "__X__",
  "list",
  "",
  ""
);
PyObject* PyBobLearnEMJFATrainer_get_X(PyBobLearnEMJFATrainerObject* self, void*){
  BOB_TRY
  return vector_as_list(self->cxx->getX());
  BOB_CATCH_MEMBER("__X__ could not be read", 0)
}
int PyBobLearnEMJFATrainer_set_X(PyBobLearnEMJFATrainerObject* self, PyObject* value, void*){
  BOB_TRY

  // Parses input arguments in a single shot
  if (!PyList_Check(value)){
    PyErr_Format(PyExc_TypeError, "Expected a list in `%s'", __X__.name());
    return -1;
  }

  std::vector<blitz::Array<double,2> > data;
  if(list_as_vector(value ,data)==0){
    self->cxx->setX(data);
  }

  return 0;
  BOB_CATCH_MEMBER("__X__ could not be written", -1)
}

static auto __Y__ = bob::extension::VariableDoc(
  "__Y__",
  "list",
  "",
  ""
);
PyObject* PyBobLearnEMJFATrainer_get_Y(PyBobLearnEMJFATrainerObject* self, void*){
  BOB_TRY
  return vector_as_list(self->cxx->getY());
  BOB_CATCH_MEMBER("__Y__ could not be read", 0)
}
int PyBobLearnEMJFATrainer_set_Y(PyBobLearnEMJFATrainerObject* self, PyObject* value, void*){
  BOB_TRY

  // Parses input arguments in a single shot
  if (!PyList_Check(value)){
    PyErr_Format(PyExc_TypeError, "Expected a list in `%s'", __Y__.name());
    return -1;
  }

  std::vector<blitz::Array<double,1> > data;
  if(list_as_vector(value ,data)==0){
    self->cxx->setY(data);
  }

  return 0;
  BOB_CATCH_MEMBER("__Y__ could not be written", -1)
}

static auto __Z__ = bob::extension::VariableDoc(
  "__Z__",
  "list",
  "",
  ""
);
PyObject* PyBobLearnEMJFATrainer_get_Z(PyBobLearnEMJFATrainerObject* self, void*){
  BOB_TRY
  return vector_as_list(self->cxx->getZ());
  BOB_CATCH_MEMBER("__Z__ could not be read", 0)
}
int PyBobLearnEMJFATrainer_set_Z(PyBobLearnEMJFATrainerObject* self, PyObject* value, void*){
  BOB_TRY

  // Parses input arguments in a single shot
  if (!PyList_Check(value)){
    PyErr_Format(PyExc_TypeError, "Expected a list in `%s'", __Z__.name());
    return -1;
  }

  std::vector<blitz::Array<double,1> > data;
  if(list_as_vector(value ,data)==0){
    self->cxx->setZ(data);
  }

  return 0;
  BOB_CATCH_MEMBER("__Z__ could not be written", -1)
}

static PyGetSetDef PyBobLearnEMJFATrainer_getseters[] = {
  {
   acc_v_a1.name(),
   (getter)PyBobLearnEMJFATrainer_get_acc_v_a1,
   (setter)PyBobLearnEMJFATrainer_set_acc_v_a1,
   acc_v_a1.doc(),
   0
  },
  {
   acc_v_a2.name(),
   (getter)PyBobLearnEMJFATrainer_get_acc_v_a2,
   (setter)PyBobLearnEMJFATrainer_set_acc_v_a2,
   acc_v_a2.doc(),
   0
  },
  {
   acc_u_a1.name(),
   (getter)PyBobLearnEMJFATrainer_get_acc_u_a1,
   (setter)PyBobLearnEMJFATrainer_set_acc_u_a1,
   acc_u_a1.doc(),
   0
  },
  {
   acc_u_a2.name(),
   (getter)PyBobLearnEMJFATrainer_get_acc_u_a2,
   (setter)PyBobLearnEMJFATrainer_set_acc_u_a2,
   acc_u_a2.doc(),
   0
  },
  {
   acc_d_a1.name(),
   (getter)PyBobLearnEMJFATrainer_get_acc_d_a1,
   (setter)PyBobLearnEMJFATrainer_set_acc_d_a1,
   acc_d_a1.doc(),
   0
  },
  {
   acc_d_a2.name(),
   (getter)PyBobLearnEMJFATrainer_get_acc_d_a2,
   (setter)PyBobLearnEMJFATrainer_set_acc_d_a2,
   acc_d_a2.doc(),
   0
  },
  {
   __X__.name(),
   (getter)PyBobLearnEMJFATrainer_get_X,
   (setter)PyBobLearnEMJFATrainer_set_X,
   __X__.doc(),
   0
  },
  {
   __Y__.name(),
   (getter)PyBobLearnEMJFATrainer_get_Y,
   (setter)PyBobLearnEMJFATrainer_set_Y,
   __Y__.doc(),
   0
  },
  {
   __Z__.name(),
   (getter)PyBobLearnEMJFATrainer_get_Z,
   (setter)PyBobLearnEMJFATrainer_set_Z,
   __Z__.doc(),
   0
  },

  {0}  // Sentinel
};

/******************************************************************/
/************ Functions Section ***********************************/
/******************************************************************/

/*** initialize ***/
static auto initialize = bob::extension::FunctionDoc(
  "initialize",
  "Initialization before the EM steps",
  "",
  true
)
.add_prototype("jfa_base, stats, [rng]")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object")
.add_parameter("rng", ":py:class:`bob.core.random.mt19937`", "The Mersenne Twister mt19937 random generator used for the initialization of subspaces/arrays before the EM loop.");
static PyObject* PyBobLearnEMJFATrainer_initialize(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  /* Parses input arguments in a single shot */
  char** kwlist = initialize.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;
  PyBoostMt19937Object* rng = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!|O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats,
                                                                 &PyBoostMt19937_Type, &rng)) return 0;

  if(rng){
    self->cxx->setRng(rng->rng);
  }

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->initialize(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the initialize method", 0)

  Py_RETURN_NONE;
}

/*** e_stepv ***/
static auto e_step_v = bob::extension::FunctionDoc(
  "e_step_v",
  "Call the 1st e-step procedure (for the V subspace).",
  "",
  true
)
.add_prototype("jfa_base,stats")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object");
static PyObject* PyBobLearnEMJFATrainer_e_step_v(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  //Parses input arguments in a single shot
  char** kwlist = e_step_v.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats)) return 0;

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->eStep1(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the e_step_v method", 0)

  Py_RETURN_NONE;
}

/*** m_step_v ***/
static auto m_step_v = bob::extension::FunctionDoc(
  "m_step_v",
  "Call the 1st m-step procedure (for the V subspace).",
  "",
  true
)
.add_prototype("jfa_base,stats")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object");
static PyObject* PyBobLearnEMJFATrainer_m_step_v(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  // Parses input arguments in a single shot
  char** kwlist = m_step_v.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats)) return 0;

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->mStep1(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the m_step_v method", 0)

  Py_RETURN_NONE;
}

/*** finalize_v ***/
static auto finalize_v = bob::extension::FunctionDoc(
  "finalize_v",
  "Call the 1st finalize procedure (for the V subspace).",
  "",
  true
)
.add_prototype("jfa_base,stats")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object");
static PyObject* PyBobLearnEMJFATrainer_finalize_v(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  //Parses input arguments in a single shot
  char** kwlist = finalize_v.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats)) return 0;

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->finalize1(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the finalize_v method", 0)

  Py_RETURN_NONE;
}

/*** e_step_u ***/
static auto e_step_u = bob::extension::FunctionDoc(
  "e_step_u",
  "Call the 2nd e-step procedure (for the U subspace).",
  "",
  true
)
.add_prototype("jfa_base,stats")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object");
static PyObject* PyBobLearnEMJFATrainer_e_step_u(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  // Parses input arguments in a single shot
  char** kwlist = e_step_u.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats)) return 0;

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->eStep2(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the e_step_u method", 0)

  Py_RETURN_NONE;
}

/*** m_step_u ***/
static auto m_step_u = bob::extension::FunctionDoc(
  "m_step_u",
  "Call the 2nd m-step procedure (for the U subspace).",
  "",
  true
)
.add_prototype("jfa_base,stats")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object");
static PyObject* PyBobLearnEMJFATrainer_m_step_u(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  // Parses input arguments in a single shot
  char** kwlist = m_step_u.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats)) return 0;

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->mStep2(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the m_step_u method", 0)

  Py_RETURN_NONE;
}

/*** finalize_u ***/
static auto finalize_u = bob::extension::FunctionDoc(
  "finalize_u",
  "Call the 2nd finalize procedure (for the U subspace).",
  "",
  true
)
.add_prototype("jfa_base,stats")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object");
static PyObject* PyBobLearnEMJFATrainer_finalize_u(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  // Parses input arguments in a single shot
  char** kwlist = finalize_u.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats)) return 0;

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->finalize2(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the finalize_u method", 0)

  Py_RETURN_NONE;
}

/*** e_step_d ***/
static auto e_step_d = bob::extension::FunctionDoc(
  "e_step_d",
  "Call the 3rd e-step procedure (for the d subspace).",
  "",
  true
)
.add_prototype("jfa_base,stats")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object");
static PyObject* PyBobLearnEMJFATrainer_e_step_d(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  // Parses input arguments in a single shot
  char** kwlist = e_step_d.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats)) return 0;

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->eStep3(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the e_step_d method", 0)

  Py_RETURN_NONE;
}

/*** m_step_d ***/
static auto m_step_d = bob::extension::FunctionDoc(
  "m_step_d",
  "Call the 3rd m-step procedure (for the d subspace).",
  "",
  true
)
.add_prototype("jfa_base,stats")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object");
static PyObject* PyBobLearnEMJFATrainer_m_step_d(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  // Parses input arguments in a single shot
  char** kwlist = m_step_d.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats)) return 0;

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->mStep3(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the m_step_d method", 0)

  Py_RETURN_NONE;
}

/*** finalize_d ***/
static auto finalize_d = bob::extension::FunctionDoc(
  "finalize_d",
  "Call the 3rd finalize procedure (for the d subspace).",
  "",
  true
)
.add_prototype("jfa_base,stats")
.add_parameter("jfa_base", ":py:class:`bob.learn.em.JFABase`", "JFABase Object")
.add_parameter("stats", ":py:class:`bob.learn.em.GMMStats`", "GMMStats Object");
static PyObject* PyBobLearnEMJFATrainer_finalize_d(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  // Parses input arguments in a single shot
  char** kwlist = finalize_d.kwlist(0);

  PyBobLearnEMJFABaseObject* jfa_base = 0;
  PyObject* stats = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!", kwlist, &PyBobLearnEMJFABase_Type, &jfa_base,
                                                                 &PyList_Type, &stats)) return 0;

  std::vector<std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > > training_data;
  if(extract_GMMStats_2d(stats ,training_data)==0)
    self->cxx->finalize3(*jfa_base->cxx, training_data);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the finalize_d method", 0)

  Py_RETURN_NONE;
}

/*** enroll ***/
static auto enroll = bob::extension::FunctionDoc(
  "enroll",
  "",
  "",
  true
)
.add_prototype("jfa_machine,features,n_iter")
.add_parameter("jfa_machine", ":py:class:`bob.learn.em.JFAMachine`", "JFAMachine Object")
.add_parameter("features", "[:py:class:`bob.learn.em.GMMStats`]", "")
.add_parameter("n_iter", "int", "Number of iterations");
static PyObject* PyBobLearnEMJFATrainer_enroll(PyBobLearnEMJFATrainerObject* self, PyObject* args, PyObject* kwargs) {
  BOB_TRY

  // Parses input arguments in a single shot
  char** kwlist = enroll.kwlist(0);

  PyBobLearnEMJFAMachineObject* jfa_machine = 0;
  PyObject* stats = 0;
  int n_iter = 1;

  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!i", kwlist, &PyBobLearnEMJFAMachine_Type, &jfa_machine,
                                                                  &PyList_Type, &stats, &n_iter)) return 0;

  std::vector<boost::shared_ptr<bob::learn::em::GMMStats> > training_data;
  if(extract_GMMStats_1d(stats ,training_data)==0)
    self->cxx->enroll(*jfa_machine->cxx, training_data, n_iter);
  else
    return 0;

  BOB_CATCH_MEMBER("cannot perform the enroll method", 0)

  Py_RETURN_NONE;
}

static PyMethodDef PyBobLearnEMJFATrainer_methods[] = {
  {
    initialize.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_initialize,
    METH_VARARGS|METH_KEYWORDS,
    initialize.doc()
  },
  {
    e_step_v.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_e_step_v,
    METH_VARARGS|METH_KEYWORDS,
    e_step_v.doc()
  },
  {
    e_step_u.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_e_step_u,
    METH_VARARGS|METH_KEYWORDS,
    e_step_u.doc()
  },
  {
    e_step_d.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_e_step_d,
    METH_VARARGS|METH_KEYWORDS,
    e_step_d.doc()
  },
  {
    m_step_v.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_m_step_v,
    METH_VARARGS|METH_KEYWORDS,
    m_step_v.doc()
  },
  {
    m_step_u.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_m_step_u,
    METH_VARARGS|METH_KEYWORDS,
    m_step_u.doc()
  },
  {
    m_step_d.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_m_step_d,
    METH_VARARGS|METH_KEYWORDS,
    m_step_d.doc()
  },
  {
    finalize_v.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_finalize_v,
    METH_VARARGS|METH_KEYWORDS,
    finalize_v.doc()
  },
  {
    finalize_u.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_finalize_u,
    METH_VARARGS|METH_KEYWORDS,
    finalize_u.doc()
  },
  {
    finalize_d.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_finalize_d,
    METH_VARARGS|METH_KEYWORDS,
    finalize_d.doc()
  },
  {
    enroll.name(),
    (PyCFunction)PyBobLearnEMJFATrainer_enroll,
    METH_VARARGS|METH_KEYWORDS,
    enroll.doc()
  },
  {0} /* Sentinel */
};

/******************************************************************/
/************ Module Section **************************************/
/******************************************************************/

// Define the Gaussian type struct; will be initialized later
PyTypeObject PyBobLearnEMJFATrainer_Type = {
  PyVarObject_HEAD_INIT(0,0)
  0
};

bool init_BobLearnEMJFATrainer(PyObject* module)
{
  // initialize the type JFATrainer
  PyBobLearnEMJFATrainer_Type.tp_name      = JFATrainer_doc.name();
  PyBobLearnEMJFATrainer_Type.tp_basicsize = sizeof(PyBobLearnEMJFATrainerObject);
  PyBobLearnEMJFATrainer_Type.tp_flags     = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;//Enable the class inheritance;
  PyBobLearnEMJFATrainer_Type.tp_doc       = JFATrainer_doc.doc();

  // set the functions
  PyBobLearnEMJFATrainer_Type.tp_new          = PyType_GenericNew;
  PyBobLearnEMJFATrainer_Type.tp_init         = reinterpret_cast<initproc>(PyBobLearnEMJFATrainer_init);
  PyBobLearnEMJFATrainer_Type.tp_dealloc      = reinterpret_cast<destructor>(PyBobLearnEMJFATrainer_delete);
  PyBobLearnEMJFATrainer_Type.tp_richcompare = reinterpret_cast<richcmpfunc>(PyBobLearnEMJFATrainer_RichCompare);
  PyBobLearnEMJFATrainer_Type.tp_methods      = PyBobLearnEMJFATrainer_methods;
  PyBobLearnEMJFATrainer_Type.tp_getset       = PyBobLearnEMJFATrainer_getseters;
  //PyBobLearnEMJFATrainer_Type.tp_call         = reinterpret_cast<ternaryfunc>(PyBobLearnEMJFATrainer_compute_likelihood);

  // check that everything is fine
  if (PyType_Ready(&PyBobLearnEMJFATrainer_Type) < 0) return false;

  // add the type to the module
  Py_INCREF(&PyBobLearnEMJFATrainer_Type);
  return PyModule_AddObject(module, "JFATrainer", (PyObject*)&PyBobLearnEMJFATrainer_Type) >= 0;
}


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2011 Gael Guennebaud <g.gael@free.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#define EIGEN_NO_DEBUG_SMALL_PRODUCT_BLOCKS
#include "sparse_solver.h"

#include <Eigen/KLUSupport>

template<typename T> void test_klu_support_T()
{
  KLU<SparseMatrix<T, ColMajor> > klu_colmajor;
  KLU<SparseMatrix<T, RowMajor> > klu_rowmajor;

  check_sparse_square_solving(klu_colmajor);
  check_sparse_square_solving(klu_rowmajor);

  //check_sparse_square_determinant(umfpack_colmajor);
  //check_sparse_square_determinant(umfpack_rowmajor);
}

void test_klu_support()
{
  CALL_SUBTEST_1(test_klu_support_T<double>());
  CALL_SUBTEST_2(test_klu_support_T<std::complex<double> >());
}


//==================================================================================================
/*!
  @file

  @copyright 2012-2015 NumScale SAS
  @copyright 2015 J.T.Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_SCALAR_FREXP_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_SCALAR_FREXP_HPP_INCLUDED

#include <boost/simd/function/definition/frexp.hpp>
#include <boost/simd/arch/common/scalar/function/frexp.hpp>
#include <boost/simd/arch/common/generic/function/frexp.hpp>

#endif


// __BEGIN_LICENSE__
// Copyright (C) 2006-2010 United States Government as represented by
// the Administrator of the National Aeronautics and Space Administration.
// All Rights Reserved.
// __END_LICENSE__

// TestCorrelator.h
#include <gtest/gtest.h>

#include <vw/Image/UtilityViews.h>
#include <vw/Stereo/CorrelatorView.h>
#include <vw/Image/Transform.h>

#include <boost/random/linear_congruential.hpp>

using namespace vw;
using namespace vw::stereo;

//   void test_image_sum_functions() {
//     ImageView<uint8> image = ConstantView<uint8>(1, 100, 100);
//     int kern_width = 10;
//     int kern_height = 10;

//     // Computes the complete sum at every pixel
//     for (int j = kern_height/2; j < image.rows()-kern_height/2; ++j)
//       for (int i = kern_width/2; i < image.cols()-kern_width/2; ++i) {
//         int16 sum = compute_sum(image, BBox2i(i-kern_width/2,j-kern_height/2,kern_width,kern_height));
//         TS_ASSERT_EQUALS(sum,kern_width*kern_height);
//       }

//     // Computes the complete sum at the beginning of the scanline, and
//     // updates as it moves across the image.
//     for (int j = kern_height/2; j < image.rows()-kern_height/2; ++j) {
//       bool need_to_compute_full_sum = true;
//       for (int i = kern_width/2; i < image.cols()-kern_width/2; ++i) {
//         int16 sum;
//         if (need_to_compute_full_sum) {
//           sum = compute_sum(image, BBox2i(i-kern_width/2,j-kern_height/2,kern_width,kern_height));
//           need_to_compute_full_sum = false;
//         } else {
//           sum = update_sum(image, sum, BBox2i(i-kern_width/2,j-kern_height/2,kern_width,kern_height));
//         }
//         TS_ASSERT_EQUALS(sum,kern_width*kern_height);
//       }
//     }
//   }

class BasicCorrelationTest : public ::testing::Test {
protected:
  BasicCorrelationTest() {}

  virtual void SetUp() {
    boost::rand48 gen(10);
    image1 = 255*uniform_noise_view( gen, 50, 50 );
    image2 = transform(image1, TranslateTransform(3,3),
                       ZeroEdgeExtension(), NearestPixelInterpolation());
    mask.set_size(50,50);
    fill(mask,PixelMask<uint8>(255));
  }

  template <class ViewT, class MViewT, class PreProcT>
  CorrelatorView<typename ViewT::pixel_type,
                 typename MViewT::pixel_type,
                 PreProcT>
  correlate( ImageViewBase<ViewT> const& input1,
             ImageViewBase<ViewT> const& input2,
             ImageViewBase<MViewT> const& mask,
             PreProcT const& proc) {
    typedef typename ViewT::pixel_type pixel_type;
    typedef typename MViewT::pixel_type mask_type;
    CorrelatorView<pixel_type,mask_type,PreProcT> corr( input1, input2, mask, mask, proc, false );
    corr.set_search_range( BBox2i(0,0,6,6) );
    corr.set_kernel_size(  Vector2i(7,7)   );
    return corr;
  }

  template <class ViewT>
  void check_error( ImageViewBase<ViewT> const& input,
                    float success = 0.9 ) {
    ViewT const& disparity_map = input.impl();
    int count_correct = 0;
    int count_valid = 0;
    for (int j = 0; j < disparity_map.rows(); ++j)
      for (int i = 0; i < disparity_map.cols(); ++i)
        if ( is_valid( disparity_map(i,j) ) ) {
          count_valid++;
          if ( disparity_map(i,j).child() == Vector2f(3,3) )
            count_correct++;
        }
    EXPECT_LT( success, float(count_correct)/float(count_valid) );
  }

  ImageView<uint8> image1, image2;
  ImageView<PixelMask<uint8> > mask;
};

TEST_F( BasicCorrelationTest, NullPreprocess ) {
  ImageView<PixelMask<Vector2f> > disparity_map =
    correlate( image1, image2, mask,
               NullStereoPreprocessingFilter() );
  check_error( disparity_map, 0.95 );
}

TEST_F( BasicCorrelationTest, SlogPreprocess ) {
  ImageView<PixelMask<Vector2f> > disparity_map =
    correlate( image1, image2, mask,
               SlogStereoPreprocessingFilter() );
  check_error( disparity_map, 0.80 );
}

TEST_F( BasicCorrelationTest, LogPreprocess ) {
  ImageView<PixelMask<Vector2f> > disparity_map =
    correlate( image1, image2, mask,
               LogStereoPreprocessingFilter() );
  check_error( disparity_map, 0.80 );
}

TEST_F( BasicCorrelationTest, BlurPreprocess ) {
  ImageView<PixelMask<Vector2f> > disparity_map =
    correlate( image1, image2, mask,
               BlurStereoPreprocessingFilter() );
  check_error( disparity_map, 0.75 );
}