if (roadLenKm != 0.0)
    {
      m_forwardSpeed = m_backwardSpeed =
          SpeedKMpH(std::min(vehicleModel.GetMaxWeightSpeed(), roadLenKm / durationH));
    }

#include "routing/geometry.hpp"

#include "routing/city_roads.hpp"
#include "routing/maxspeeds.hpp"
#include "routing/routing_exceptions.hpp"
#include "routing/routing_options.hpp"

#include "indexer/altitude_loader.hpp"
#include "indexer/data_source.hpp"
#include "indexer/ftypes_matcher.hpp"

#include "geometry/distance_on_sphere.hpp"
#include "geometry/mercator.hpp"

#include "base/assert.hpp"
#include "base/string_utils.hpp"

#include <algorithm>
#include <string>

using namespace routing;
using namespace std;

namespace
{

double CalcFerryDurationHours(string const & durationHours, double roadLenKm)
{
  // Shortly: the coefs were received from statistic about ferries with durations in OSM.
  double constexpr kIntercept = 0.2490726747447476;
  double constexpr kSlope = 0.02078913;

  if (durationHours.empty())
    return kIntercept + kSlope * roadLenKm;

  double durationH = 0.0;
  CHECK(strings::to_double(durationHours.c_str(), durationH), (durationHours));

  // Shortly: we drop some points: (x: lengthKm, y: durationH), that are upper or lower these two
  double constexpr kUpperBoundIntercept = 4.0;
  double constexpr kUpperBoundSlope = 0.037;
  if (kUpperBoundIntercept + kUpperBoundSlope * roadLenKm - durationH < 0)
    return kIntercept + kSlope * roadLenKm;

  double constexpr kLowerBoundIntercept = -2.0;
  double constexpr kLowerBoundSlope = 0.015;
  if (kLowerBoundIntercept + kLowerBoundSlope * roadLenKm - durationH > 0)
    return kIntercept + kSlope * roadLenKm;

  return durationH;
}

class GeometryLoaderImpl final : public GeometryLoader
{
public:
  GeometryLoaderImpl(DataSource const & dataSource, MwmSet::MwmHandle const & handle,
                     shared_ptr<VehicleModelInterface> vehicleModel, AttrLoader attrLoader,
                     bool loadAltitudes);

  // GeometryLoader overrides:
  void Load(uint32_t featureId, RoadGeometry & road) override;

private:
  shared_ptr<VehicleModelInterface> m_vehicleModel;
  AttrLoader m_attrLoader;
  FeaturesLoaderGuard m_guard;
  string const m_country;
  feature::AltitudeLoader m_altitudeLoader;
  bool const m_loadAltitudes;
};

GeometryLoaderImpl::GeometryLoaderImpl(DataSource const & dataSource,
                                       MwmSet::MwmHandle const & handle,
                                       shared_ptr<VehicleModelInterface> vehicleModel,
                                       AttrLoader attrLoader, bool loadAltitudes)
  : m_vehicleModel(move(vehicleModel))
  , m_attrLoader(move(attrLoader))
  , m_guard(dataSource, handle.GetId())
  , m_country(handle.GetInfo()->GetCountryName())
  , m_altitudeLoader(dataSource, handle.GetId())
  , m_loadAltitudes(loadAltitudes)
{
  CHECK(handle.IsAlive(), ());
  CHECK(m_vehicleModel, ());
  CHECK(m_attrLoader.m_cityRoads, ());
  CHECK(m_attrLoader.m_maxspeeds, ());
}

void GeometryLoaderImpl::Load(uint32_t featureId, RoadGeometry & road)
{
  auto feature = m_guard.GetFeatureByIndex(featureId);
  if (!feature)
    MYTHROW(RoutingException, ("Feature", featureId, "not found in ", m_country));

  feature->ParseGeometry(FeatureType::BEST_GEOMETRY);

  geometry::Altitudes const * altitudes = nullptr;
  if (m_loadAltitudes)
    altitudes = &(m_altitudeLoader.GetAltitudes(featureId, feature->GetPointsCount()));

  road.Load(*m_vehicleModel, *feature, altitudes, m_attrLoader.m_cityRoads->IsCityRoad(featureId),
            m_attrLoader.m_maxspeeds->GetMaxspeed(featureId));
  m_altitudeLoader.ClearCache();
}

class FileGeometryLoader final : public GeometryLoader
{
public:
  FileGeometryLoader(string const & fileName, shared_ptr<VehicleModelInterface> vehicleModel);

  // GeometryLoader overrides:
  void Load(uint32_t featureId, RoadGeometry & road) override;

private:
  FeaturesVectorTest m_featuresVector;
  CityRoads m_cityRoads;
  Maxspeeds m_maxspeeds;
  shared_ptr<VehicleModelInterface> m_vehicleModel;
};

FileGeometryLoader::FileGeometryLoader(string const & fileName,
                                       shared_ptr<VehicleModelInterface> vehicleModel)
  : m_featuresVector(FilesContainerR(make_unique<FileReader>(fileName)))
  , m_vehicleModel(vehicleModel)
{
  auto const cont = FilesContainerR(make_unique<FileReader>(fileName));

  try
  {
    if (cont.IsExist(CITY_ROADS_FILE_TAG))
      LoadCityRoads(fileName, cont.GetReader(CITY_ROADS_FILE_TAG), m_cityRoads);

    if (cont.IsExist(MAXSPEEDS_FILE_TAG))
      LoadMaxspeeds(cont.GetReader(MAXSPEEDS_FILE_TAG), m_maxspeeds);
  }
  catch (Reader::OpenException const & e)
  {
    LOG(LERROR, ("File", cont.GetFileName(), "Error while reading", CITY_ROADS_FILE_TAG, "or",
                 MAXSPEEDS_FILE_TAG, "section.", e.Msg()));
  }

  CHECK(m_vehicleModel, ());
}

void FileGeometryLoader::Load(uint32_t featureId, RoadGeometry & road)
{
  auto feature = m_featuresVector.GetVector().GetByIndex(featureId);
  CHECK(feature, ());
  feature->ParseGeometry(FeatureType::BEST_GEOMETRY);
  // Note. If FileGeometryLoader is used for generation cross mwm section for bicycle or
  // pedestrian routing |altitudes| should be used here.
  road.Load(*m_vehicleModel, *feature, nullptr /* altitudes */, m_cityRoads.IsCityRoad(featureId),
            m_maxspeeds.GetMaxspeed(featureId));
}
}  // namespace

namespace routing
{
RoadGeometry::RoadGeometry(bool oneWay, double weightSpeedKMpH, double etaSpeedKMpH,
                           Points const & points)
  : m_forwardSpeed{weightSpeedKMpH, etaSpeedKMpH}
  , m_backwardSpeed(m_forwardSpeed)
  , m_isOneWay(oneWay)
  , m_valid(true)
{
  ASSERT_GREATER(weightSpeedKMpH, 0.0, ());
  ASSERT_GREATER(etaSpeedKMpH, 0.0, ());

  m_junctions.reserve(points.size());
  for (auto const & point : points)
    m_junctions.emplace_back(mercator::ToLatLon(point), geometry::kDefaultAltitudeMeters);
}

void RoadGeometry::Load(VehicleModelInterface const & vehicleModel, FeatureType & feature,
                        geometry::Altitudes const * altitudes, bool inCity,
                        Maxspeed const & maxspeed)
{
  CHECK(altitudes == nullptr || altitudes->size() == feature.GetPointsCount(), ());

  m_valid = vehicleModel.IsRoad(feature);
  m_isOneWay = vehicleModel.IsOneWay(feature);
  m_forwardSpeed = vehicleModel.GetSpeed(feature, {true /* forward */, inCity, maxspeed});
  m_backwardSpeed = vehicleModel.GetSpeed(feature, {false /* forward */, inCity, maxspeed});
  m_highwayType = vehicleModel.GetHighwayType(feature);
  m_isPassThroughAllowed = vehicleModel.IsPassThroughAllowed(feature);

  feature::TypesHolder types(feature);
  auto const & optionsClassfier = RoutingOptionsClassifier::Instance();
  for (uint32_t type : types)
  {
    if (auto const it = optionsClassfier.Get(type))
      m_routingOptions.Add(*it);
  }

  m_junctions.clear();
  m_junctions.reserve(feature.GetPointsCount());
  for (size_t i = 0; i < feature.GetPointsCount(); ++i)
  {
    m_junctions.emplace_back(mercator::ToLatLon(feature.GetPoint(i)),
                             altitudes ? (*altitudes)[i] : geometry::kDefaultAltitudeMeters);
  }

  if (m_routingOptions.Has(RoutingOptions::Road::Ferry))
  {
    auto const durationHours = feature.GetMetadata(feature::Metadata::FMD_DURATION);
    auto const roadLenKm = GetRoadLengthM() / 1000.0;
    double const durationH = CalcFerryDurationHours(durationHours, roadLenKm);

    CHECK(!base::AlmostEqualAbs(durationH, 0.0, 1e-5), (durationH));

  }

  if (m_valid && (!m_forwardSpeed.IsValid() || !m_backwardSpeed.IsValid()))
  {
    auto const & id = feature.GetID();
    CHECK(!m_junctions.empty(), ("mwm:", id.GetMwmName(), ", featureId:", id.m_index));
    auto const & begin = m_junctions.front().GetLatLon();
    auto const & end = m_junctions.back().GetLatLon();
    LOG(LERROR,
        ("Invalid speed m_forwardSpeed:", m_forwardSpeed, "m_backwardSpeed:", m_backwardSpeed,
         "mwm:", id.GetMwmName(), ", featureId:", id.m_index, ", begin:", begin, "end:", end));
    m_valid = false;
  }
}

SpeedKMpH const & RoadGeometry::GetSpeed(bool forward) const
{
  return forward ? m_forwardSpeed : m_backwardSpeed;
}

double RoadGeometry::GetRoadLengthM() const
{
  double lenM = 0.0;
  for (size_t i = 1; i < GetPointsCount(); ++i)
  {
    lenM += ms::DistanceOnEarth(m_junctions[i - 1].GetLatLon(), m_junctions[i].GetLatLon());
  }

  return lenM;
}

Geometry::Geometry(unique_ptr<GeometryLoader> loader, size_t roadsCacheSize)
  : m_loader(move(loader))
  , m_featureIdToRoad(make_unique<RoutingFifoCache>(
        roadsCacheSize,
        [this](uint32_t featureId, RoadGeometry & road) { m_loader->Load(featureId, road); }))
{
  CHECK(m_loader, ());
}

RoadGeometry const & Geometry::GetRoad(uint32_t featureId)
{
  ASSERT(m_featureIdToRoad, ());
  ASSERT(m_loader, ());

  return m_featureIdToRoad->GetValue(featureId);
}

unique_ptr<GeometryLoader> GeometryLoader::Create(DataSource const & dataSource,
                                                  MwmSet::MwmHandle const & handle,
                                                  shared_ptr<VehicleModelInterface> vehicleModel,
                                                  AttrLoader && attrLoader, bool loadAltitudes)
{
  CHECK(handle.IsAlive(), ());
  return make_unique<GeometryLoaderImpl>(dataSource, handle, vehicleModel, move(attrLoader),
                                         loadAltitudes);
}

unique_ptr<GeometryLoader> GeometryLoader::CreateFromFile(
    string const & fileName, shared_ptr<VehicleModelInterface> vehicleModel)
{
  return make_unique<FileGeometryLoader>(fileName, vehicleModel);
}
}  // namespace routing


for (size_t i = 0; i < feature.GetPointsCount(); ++i)
  {
    m_junctions.emplace_back(mercator::ToLatLon(feature.GetPoint(i)),
                             altitudes ? (*altitudes)[i] : geometry::kDefaultAltitudeMeters);
  }

#include "routing/geometry.hpp"

#include "routing/city_roads.hpp"
#include "routing/maxspeeds.hpp"
#include "routing/routing_exceptions.hpp"
#include "routing/routing_options.hpp"

#include "indexer/altitude_loader.hpp"
#include "indexer/data_source.hpp"
#include "indexer/ftypes_matcher.hpp"

#include "geometry/distance_on_sphere.hpp"
#include "geometry/mercator.hpp"

#include "base/assert.hpp"
#include "base/string_utils.hpp"

#include <algorithm>
#include <string>

using namespace routing;
using namespace std;

namespace
{

double CalcFerryDurationHours(string const & durationHours, double roadLenKm)
{
  // Shortly: the coefs were received from statistic about ferries with durations in OSM.
  double constexpr kIntercept = 0.2490726747447476;
  double constexpr kSlope = 0.02078913;

  if (durationHours.empty())
    return kIntercept + kSlope * roadLenKm;

  double durationH = 0.0;
  CHECK(strings::to_double(durationHours.c_str(), durationH), (durationHours));

  // Shortly: we drop some points: (x: lengthKm, y: durationH), that are upper or lower these two
  double constexpr kUpperBoundIntercept = 4.0;
  double constexpr kUpperBoundSlope = 0.037;
  if (kUpperBoundIntercept + kUpperBoundSlope * roadLenKm - durationH < 0)
    return kIntercept + kSlope * roadLenKm;

  double constexpr kLowerBoundIntercept = -2.0;
  double constexpr kLowerBoundSlope = 0.015;
  if (kLowerBoundIntercept + kLowerBoundSlope * roadLenKm - durationH > 0)
    return kIntercept + kSlope * roadLenKm;

  return durationH;
}

class GeometryLoaderImpl final : public GeometryLoader
{
public:
  GeometryLoaderImpl(DataSource const & dataSource, MwmSet::MwmHandle const & handle,
                     shared_ptr<VehicleModelInterface> vehicleModel, AttrLoader attrLoader,
                     bool loadAltitudes);

  // GeometryLoader overrides:
  void Load(uint32_t featureId, RoadGeometry & road) override;

private:
  shared_ptr<VehicleModelInterface> m_vehicleModel;
  AttrLoader m_attrLoader;
  FeaturesLoaderGuard m_guard;
  string const m_country;
  feature::AltitudeLoader m_altitudeLoader;
  bool const m_loadAltitudes;
};

GeometryLoaderImpl::GeometryLoaderImpl(DataSource const & dataSource,
                                       MwmSet::MwmHandle const & handle,
                                       shared_ptr<VehicleModelInterface> vehicleModel,
                                       AttrLoader attrLoader, bool loadAltitudes)
  : m_vehicleModel(move(vehicleModel))
  , m_attrLoader(move(attrLoader))
  , m_guard(dataSource, handle.GetId())
  , m_country(handle.GetInfo()->GetCountryName())
  , m_altitudeLoader(dataSource, handle.GetId())
  , m_loadAltitudes(loadAltitudes)
{
  CHECK(handle.IsAlive(), ());
  CHECK(m_vehicleModel, ());
  CHECK(m_attrLoader.m_cityRoads, ());
  CHECK(m_attrLoader.m_maxspeeds, ());
}

void GeometryLoaderImpl::Load(uint32_t featureId, RoadGeometry & road)
{
  auto feature = m_guard.GetFeatureByIndex(featureId);
  if (!feature)
    MYTHROW(RoutingException, ("Feature", featureId, "not found in ", m_country));

  feature->ParseGeometry(FeatureType::BEST_GEOMETRY);

  geometry::Altitudes const * altitudes = nullptr;
  if (m_loadAltitudes)
    altitudes = &(m_altitudeLoader.GetAltitudes(featureId, feature->GetPointsCount()));

  road.Load(*m_vehicleModel, *feature, altitudes, m_attrLoader.m_cityRoads->IsCityRoad(featureId),
            m_attrLoader.m_maxspeeds->GetMaxspeed(featureId));
  m_altitudeLoader.ClearCache();
}

class FileGeometryLoader final : public GeometryLoader
{
public:
  FileGeometryLoader(string const & fileName, shared_ptr<VehicleModelInterface> vehicleModel);

  // GeometryLoader overrides:
  void Load(uint32_t featureId, RoadGeometry & road) override;

private:
  FeaturesVectorTest m_featuresVector;
  CityRoads m_cityRoads;
  Maxspeeds m_maxspeeds;
  shared_ptr<VehicleModelInterface> m_vehicleModel;
};

FileGeometryLoader::FileGeometryLoader(string const & fileName,
                                       shared_ptr<VehicleModelInterface> vehicleModel)
  : m_featuresVector(FilesContainerR(make_unique<FileReader>(fileName)))
  , m_vehicleModel(vehicleModel)
{
  auto const cont = FilesContainerR(make_unique<FileReader>(fileName));

  try
  {
    if (cont.IsExist(CITY_ROADS_FILE_TAG))
      LoadCityRoads(fileName, cont.GetReader(CITY_ROADS_FILE_TAG), m_cityRoads);

    if (cont.IsExist(MAXSPEEDS_FILE_TAG))
      LoadMaxspeeds(cont.GetReader(MAXSPEEDS_FILE_TAG), m_maxspeeds);
  }
  catch (Reader::OpenException const & e)
  {
    LOG(LERROR, ("File", cont.GetFileName(), "Error while reading", CITY_ROADS_FILE_TAG, "or",
                 MAXSPEEDS_FILE_TAG, "section.", e.Msg()));
  }

  CHECK(m_vehicleModel, ());
}

void FileGeometryLoader::Load(uint32_t featureId, RoadGeometry & road)
{
  auto feature = m_featuresVector.GetVector().GetByIndex(featureId);
  CHECK(feature, ());
  feature->ParseGeometry(FeatureType::BEST_GEOMETRY);
  // Note. If FileGeometryLoader is used for generation cross mwm section for bicycle or
  // pedestrian routing |altitudes| should be used here.
  road.Load(*m_vehicleModel, *feature, nullptr /* altitudes */, m_cityRoads.IsCityRoad(featureId),
            m_maxspeeds.GetMaxspeed(featureId));
}
}  // namespace

namespace routing
{
RoadGeometry::RoadGeometry(bool oneWay, double weightSpeedKMpH, double etaSpeedKMpH,
                           Points const & points)
  : m_forwardSpeed{weightSpeedKMpH, etaSpeedKMpH}
  , m_backwardSpeed(m_forwardSpeed)
  , m_isOneWay(oneWay)
  , m_valid(true)
{
  ASSERT_GREATER(weightSpeedKMpH, 0.0, ());
  ASSERT_GREATER(etaSpeedKMpH, 0.0, ());

  m_junctions.reserve(points.size());
  for (auto const & point : points)
    m_junctions.emplace_back(mercator::ToLatLon(point), geometry::kDefaultAltitudeMeters);
}

void RoadGeometry::Load(VehicleModelInterface const & vehicleModel, FeatureType & feature,
                        geometry::Altitudes const * altitudes, bool inCity,
                        Maxspeed const & maxspeed)
{
  CHECK(altitudes == nullptr || altitudes->size() == feature.GetPointsCount(), ());

  m_valid = vehicleModel.IsRoad(feature);
  m_isOneWay = vehicleModel.IsOneWay(feature);
  m_forwardSpeed = vehicleModel.GetSpeed(feature, {true /* forward */, inCity, maxspeed});
  m_backwardSpeed = vehicleModel.GetSpeed(feature, {false /* forward */, inCity, maxspeed});
  m_highwayType = vehicleModel.GetHighwayType(feature);
  m_isPassThroughAllowed = vehicleModel.IsPassThroughAllowed(feature);

  feature::TypesHolder types(feature);
  auto const & optionsClassfier = RoutingOptionsClassifier::Instance();
  for (uint32_t type : types)
  {
    if (auto const it = optionsClassfier.Get(type))
      m_routingOptions.Add(*it);
  }

  m_junctions.clear();
  m_junctions.reserve(feature.GetPointsCount());

  if (m_routingOptions.Has(RoutingOptions::Road::Ferry))
  {
    auto const durationHours = feature.GetMetadata(feature::Metadata::FMD_DURATION);
    auto const roadLenKm = GetRoadLengthM() / 1000.0;
    double const durationH = CalcFerryDurationHours(durationHours, roadLenKm);

    CHECK(!base::AlmostEqualAbs(durationH, 0.0, 1e-5), (durationH));

    if (roadLenKm != 0.0)
    {
      m_forwardSpeed = m_backwardSpeed =
          SpeedKMpH(std::min(vehicleModel.GetMaxWeightSpeed(), roadLenKm / durationH));
    }
  }

  if (m_valid && (!m_forwardSpeed.IsValid() || !m_backwardSpeed.IsValid()))
  {
    auto const & id = feature.GetID();
    CHECK(!m_junctions.empty(), ("mwm:", id.GetMwmName(), ", featureId:", id.m_index));
    auto const & begin = m_junctions.front().GetLatLon();
    auto const & end = m_junctions.back().GetLatLon();
    LOG(LERROR,
        ("Invalid speed m_forwardSpeed:", m_forwardSpeed, "m_backwardSpeed:", m_backwardSpeed,
         "mwm:", id.GetMwmName(), ", featureId:", id.m_index, ", begin:", begin, "end:", end));
    m_valid = false;
  }
}

SpeedKMpH const & RoadGeometry::GetSpeed(bool forward) const
{
  return forward ? m_forwardSpeed : m_backwardSpeed;
}

double RoadGeometry::GetRoadLengthM() const
{
  double lenM = 0.0;
  for (size_t i = 1; i < GetPointsCount(); ++i)
  {
    lenM += ms::DistanceOnEarth(m_junctions[i - 1].GetLatLon(), m_junctions[i].GetLatLon());
  }

  return lenM;
}

Geometry::Geometry(unique_ptr<GeometryLoader> loader, size_t roadsCacheSize)
  : m_loader(move(loader))
  , m_featureIdToRoad(make_unique<RoutingFifoCache>(
        roadsCacheSize,
        [this](uint32_t featureId, RoadGeometry & road) { m_loader->Load(featureId, road); }))
{
  CHECK(m_loader, ());
}

RoadGeometry const & Geometry::GetRoad(uint32_t featureId)
{
  ASSERT(m_featureIdToRoad, ());
  ASSERT(m_loader, ());

  return m_featureIdToRoad->GetValue(featureId);
}

unique_ptr<GeometryLoader> GeometryLoader::Create(DataSource const & dataSource,
                                                  MwmSet::MwmHandle const & handle,
                                                  shared_ptr<VehicleModelInterface> vehicleModel,
                                                  AttrLoader && attrLoader, bool loadAltitudes)
{
  CHECK(handle.IsAlive(), ());
  return make_unique<GeometryLoaderImpl>(dataSource, handle, vehicleModel, move(attrLoader),
                                         loadAltitudes);
}

unique_ptr<GeometryLoader> GeometryLoader::CreateFromFile(
    string const & fileName, shared_ptr<VehicleModelInterface> vehicleModel)
{
  return make_unique<FileGeometryLoader>(fileName, vehicleModel);
}
}  // namespace routing


try
  {
    if (cont.IsExist(CITY_ROADS_FILE_TAG))
      LoadCityRoads(fileName, cont.GetReader(CITY_ROADS_FILE_TAG), m_cityRoads);

    if (cont.IsExist(MAXSPEEDS_FILE_TAG))
      LoadMaxspeeds(cont.GetReader(MAXSPEEDS_FILE_TAG), m_maxspeeds);
  }
  catch (Reader::OpenException const & e)
  {
    LOG(LERROR, ("File", cont.GetFileName(), "Error while reading", CITY_ROADS_FILE_TAG, "or",
                 MAXSPEEDS_FILE_TAG, "section.", e.Msg()));
  }

#include "routing/geometry.hpp"

#include "routing/city_roads.hpp"
#include "routing/maxspeeds.hpp"
#include "routing/routing_exceptions.hpp"
#include "routing/routing_options.hpp"

#include "indexer/altitude_loader.hpp"
#include "indexer/data_source.hpp"
#include "indexer/ftypes_matcher.hpp"

#include "geometry/distance_on_sphere.hpp"
#include "geometry/mercator.hpp"

#include "base/assert.hpp"
#include "base/string_utils.hpp"

#include <algorithm>
#include <string>

using namespace routing;
using namespace std;

namespace
{

double CalcFerryDurationHours(string const & durationHours, double roadLenKm)
{
  // Shortly: the coefs were received from statistic about ferries with durations in OSM.
  double constexpr kIntercept = 0.2490726747447476;
  double constexpr kSlope = 0.02078913;

  if (durationHours.empty())
    return kIntercept + kSlope * roadLenKm;

  double durationH = 0.0;
  CHECK(strings::to_double(durationHours.c_str(), durationH), (durationHours));

  // Shortly: we drop some points: (x: lengthKm, y: durationH), that are upper or lower these two
  double constexpr kUpperBoundIntercept = 4.0;
  double constexpr kUpperBoundSlope = 0.037;
  if (kUpperBoundIntercept + kUpperBoundSlope * roadLenKm - durationH < 0)
    return kIntercept + kSlope * roadLenKm;

  double constexpr kLowerBoundIntercept = -2.0;
  double constexpr kLowerBoundSlope = 0.015;
  if (kLowerBoundIntercept + kLowerBoundSlope * roadLenKm - durationH > 0)
    return kIntercept + kSlope * roadLenKm;

  return durationH;
}

class GeometryLoaderImpl final : public GeometryLoader
{
public:
  GeometryLoaderImpl(DataSource const & dataSource, MwmSet::MwmHandle const & handle,
                     shared_ptr<VehicleModelInterface> vehicleModel, AttrLoader attrLoader,
                     bool loadAltitudes);

  // GeometryLoader overrides:
  void Load(uint32_t featureId, RoadGeometry & road) override;

private:
  shared_ptr<VehicleModelInterface> m_vehicleModel;
  AttrLoader m_attrLoader;
  FeaturesLoaderGuard m_guard;
  string const m_country;
  feature::AltitudeLoader m_altitudeLoader;
  bool const m_loadAltitudes;
};

GeometryLoaderImpl::GeometryLoaderImpl(DataSource const & dataSource,
                                       MwmSet::MwmHandle const & handle,
                                       shared_ptr<VehicleModelInterface> vehicleModel,
                                       AttrLoader attrLoader, bool loadAltitudes)
  : m_vehicleModel(move(vehicleModel))
  , m_attrLoader(move(attrLoader))
  , m_guard(dataSource, handle.GetId())
  , m_country(handle.GetInfo()->GetCountryName())
  , m_altitudeLoader(dataSource, handle.GetId())
  , m_loadAltitudes(loadAltitudes)
{
  CHECK(handle.IsAlive(), ());
  CHECK(m_vehicleModel, ());
  CHECK(m_attrLoader.m_cityRoads, ());
  CHECK(m_attrLoader.m_maxspeeds, ());
}

void GeometryLoaderImpl::Load(uint32_t featureId, RoadGeometry & road)
{
  auto feature = m_guard.GetFeatureByIndex(featureId);
  if (!feature)
    MYTHROW(RoutingException, ("Feature", featureId, "not found in ", m_country));

  feature->ParseGeometry(FeatureType::BEST_GEOMETRY);

  geometry::Altitudes const * altitudes = nullptr;
  if (m_loadAltitudes)
    altitudes = &(m_altitudeLoader.GetAltitudes(featureId, feature->GetPointsCount()));

  road.Load(*m_vehicleModel, *feature, altitudes, m_attrLoader.m_cityRoads->IsCityRoad(featureId),
            m_attrLoader.m_maxspeeds->GetMaxspeed(featureId));
  m_altitudeLoader.ClearCache();
}

class FileGeometryLoader final : public GeometryLoader
{
public:
  FileGeometryLoader(string const & fileName, shared_ptr<VehicleModelInterface> vehicleModel);

  // GeometryLoader overrides:
  void Load(uint32_t featureId, RoadGeometry & road) override;

private:
  FeaturesVectorTest m_featuresVector;
  CityRoads m_cityRoads;
  Maxspeeds m_maxspeeds;
  shared_ptr<VehicleModelInterface> m_vehicleModel;
};

FileGeometryLoader::FileGeometryLoader(string const & fileName,
                                       shared_ptr<VehicleModelInterface> vehicleModel)
  : m_featuresVector(FilesContainerR(make_unique<FileReader>(fileName)))
  , m_vehicleModel(vehicleModel)
{
  auto const cont = FilesContainerR(make_unique<FileReader>(fileName));

  CHECK(m_vehicleModel, ());
}

void FileGeometryLoader::Load(uint32_t featureId, RoadGeometry & road)
{
  auto feature = m_featuresVector.GetVector().GetByIndex(featureId);
  CHECK(feature, ());
  feature->ParseGeometry(FeatureType::BEST_GEOMETRY);
  // Note. If FileGeometryLoader is used for generation cross mwm section for bicycle or
  // pedestrian routing |altitudes| should be used here.
  road.Load(*m_vehicleModel, *feature, nullptr /* altitudes */, m_cityRoads.IsCityRoad(featureId),
            m_maxspeeds.GetMaxspeed(featureId));
}
}  // namespace

namespace routing
{
RoadGeometry::RoadGeometry(bool oneWay, double weightSpeedKMpH, double etaSpeedKMpH,
                           Points const & points)
  : m_forwardSpeed{weightSpeedKMpH, etaSpeedKMpH}
  , m_backwardSpeed(m_forwardSpeed)
  , m_isOneWay(oneWay)
  , m_valid(true)
{
  ASSERT_GREATER(weightSpeedKMpH, 0.0, ());
  ASSERT_GREATER(etaSpeedKMpH, 0.0, ());

  m_junctions.reserve(points.size());
  for (auto const & point : points)
    m_junctions.emplace_back(mercator::ToLatLon(point), geometry::kDefaultAltitudeMeters);
}

void RoadGeometry::Load(VehicleModelInterface const & vehicleModel, FeatureType & feature,
                        geometry::Altitudes const * altitudes, bool inCity,
                        Maxspeed const & maxspeed)
{
  CHECK(altitudes == nullptr || altitudes->size() == feature.GetPointsCount(), ());

  m_valid = vehicleModel.IsRoad(feature);
  m_isOneWay = vehicleModel.IsOneWay(feature);
  m_forwardSpeed = vehicleModel.GetSpeed(feature, {true /* forward */, inCity, maxspeed});
  m_backwardSpeed = vehicleModel.GetSpeed(feature, {false /* forward */, inCity, maxspeed});
  m_highwayType = vehicleModel.GetHighwayType(feature);
  m_isPassThroughAllowed = vehicleModel.IsPassThroughAllowed(feature);

  feature::TypesHolder types(feature);
  auto const & optionsClassfier = RoutingOptionsClassifier::Instance();
  for (uint32_t type : types)
  {
    if (auto const it = optionsClassfier.Get(type))
      m_routingOptions.Add(*it);
  }

  m_junctions.clear();
  m_junctions.reserve(feature.GetPointsCount());
  for (size_t i = 0; i < feature.GetPointsCount(); ++i)
  {
    m_junctions.emplace_back(mercator::ToLatLon(feature.GetPoint(i)),
                             altitudes ? (*altitudes)[i] : geometry::kDefaultAltitudeMeters);
  }

  if (m_routingOptions.Has(RoutingOptions::Road::Ferry))
  {
    auto const durationHours = feature.GetMetadata(feature::Metadata::FMD_DURATION);
    auto const roadLenKm = GetRoadLengthM() / 1000.0;
    double const durationH = CalcFerryDurationHours(durationHours, roadLenKm);

    CHECK(!base::AlmostEqualAbs(durationH, 0.0, 1e-5), (durationH));

    if (roadLenKm != 0.0)
    {
      m_forwardSpeed = m_backwardSpeed =
          SpeedKMpH(std::min(vehicleModel.GetMaxWeightSpeed(), roadLenKm / durationH));
    }
  }

  if (m_valid && (!m_forwardSpeed.IsValid() || !m_backwardSpeed.IsValid()))
  {
    auto const & id = feature.GetID();
    CHECK(!m_junctions.empty(), ("mwm:", id.GetMwmName(), ", featureId:", id.m_index));
    auto const & begin = m_junctions.front().GetLatLon();
    auto const & end = m_junctions.back().GetLatLon();
    LOG(LERROR,
        ("Invalid speed m_forwardSpeed:", m_forwardSpeed, "m_backwardSpeed:", m_backwardSpeed,
         "mwm:", id.GetMwmName(), ", featureId:", id.m_index, ", begin:", begin, "end:", end));
    m_valid = false;
  }
}

SpeedKMpH const & RoadGeometry::GetSpeed(bool forward) const
{
  return forward ? m_forwardSpeed : m_backwardSpeed;
}

double RoadGeometry::GetRoadLengthM() const
{
  double lenM = 0.0;
  for (size_t i = 1; i < GetPointsCount(); ++i)
  {
    lenM += ms::DistanceOnEarth(m_junctions[i - 1].GetLatLon(), m_junctions[i].GetLatLon());
  }

  return lenM;
}

Geometry::Geometry(unique_ptr<GeometryLoader> loader, size_t roadsCacheSize)
  : m_loader(move(loader))
  , m_featureIdToRoad(make_unique<RoutingFifoCache>(
        roadsCacheSize,
        [this](uint32_t featureId, RoadGeometry & road) { m_loader->Load(featureId, road); }))
{
  CHECK(m_loader, ());
}

RoadGeometry const & Geometry::GetRoad(uint32_t featureId)
{
  ASSERT(m_featureIdToRoad, ());
  ASSERT(m_loader, ());

  return m_featureIdToRoad->GetValue(featureId);
}

unique_ptr<GeometryLoader> GeometryLoader::Create(DataSource const & dataSource,
                                                  MwmSet::MwmHandle const & handle,
                                                  shared_ptr<VehicleModelInterface> vehicleModel,
                                                  AttrLoader && attrLoader, bool loadAltitudes)
{
  CHECK(handle.IsAlive(), ());
  return make_unique<GeometryLoaderImpl>(dataSource, handle, vehicleModel, move(attrLoader),
                                         loadAltitudes);
}

unique_ptr<GeometryLoader> GeometryLoader::CreateFromFile(
    string const & fileName, shared_ptr<VehicleModelInterface> vehicleModel)
{
  return make_unique<FileGeometryLoader>(fileName, vehicleModel);
}
}  // namespace routing


#include "routing/geometry.hpp"

#include "routing/city_roads.hpp"
#include "routing/maxspeeds.hpp"
#include "routing/routing_exceptions.hpp"
#include "routing/routing_options.hpp"

#include "indexer/altitude_loader.hpp"
#include "indexer/data_source.hpp"
#include "indexer/ftypes_matcher.hpp"

#include "geometry/distance_on_sphere.hpp"
#include "geometry/mercator.hpp"

#include "base/assert.hpp"
#include "base/string_utils.hpp"

#include <algorithm>
#include <string>

using namespace routing;
using namespace std;

namespace
{

double CalcFerryDurationHours(string const & durationHours, double roadLenKm)
{
  // Shortly: the coefs were received from statistic about ferries with durations in OSM.
  double constexpr kIntercept = 0.2490726747447476;
  double constexpr kSlope = 0.02078913;

  if (durationHours.empty())
    return kIntercept + kSlope * roadLenKm;

  double durationH = 0.0;
  CHECK(strings::to_double(durationHours.c_str(), durationH), (durationHours));

  // Shortly: we drop some points: (x: lengthKm, y: durationH), that are upper or lower these two
  double constexpr kUpperBoundIntercept = 4.0;
  double constexpr kUpperBoundSlope = 0.037;
  if (kUpperBoundIntercept + kUpperBoundSlope * roadLenKm - durationH < 0)
    return kIntercept + kSlope * roadLenKm;

  double constexpr kLowerBoundIntercept = -2.0;
  double constexpr kLowerBoundSlope = 0.015;
  if (kLowerBoundIntercept + kLowerBoundSlope * roadLenKm - durationH > 0)
    return kIntercept + kSlope * roadLenKm;

  return durationH;
}

class GeometryLoaderImpl final : public GeometryLoader
{
public:
  GeometryLoaderImpl(DataSource const & dataSource, MwmSet::MwmHandle const & handle,
                     shared_ptr<VehicleModelInterface> vehicleModel, AttrLoader attrLoader,
                     bool loadAltitudes);

  // GeometryLoader overrides:
  void Load(uint32_t featureId, RoadGeometry & road) override;

private:
  shared_ptr<VehicleModelInterface> m_vehicleModel;
  AttrLoader m_attrLoader;
  FeaturesLoaderGuard m_guard;
  string const m_country;
  feature::AltitudeLoader m_altitudeLoader;
  bool const m_loadAltitudes;
};

GeometryLoaderImpl::GeometryLoaderImpl(DataSource const & dataSource,
                                       MwmSet::MwmHandle const & handle,
                                       shared_ptr<VehicleModelInterface> vehicleModel,
                                       AttrLoader attrLoader, bool loadAltitudes)
  : m_vehicleModel(move(vehicleModel))
  , m_attrLoader(move(attrLoader))
  , m_guard(dataSource, handle.GetId())
  , m_country(handle.GetInfo()->GetCountryName())
  , m_altitudeLoader(dataSource, handle.GetId())
  , m_loadAltitudes(loadAltitudes)
{
  CHECK(handle.IsAlive(), ());
  CHECK(m_vehicleModel, ());
  CHECK(m_attrLoader.m_cityRoads, ());
  CHECK(m_attrLoader.m_maxspeeds, ());
}

void GeometryLoaderImpl::Load(uint32_t featureId, RoadGeometry & road)
{
  auto feature = m_guard.GetFeatureByIndex(featureId);
  if (!feature)
    MYTHROW(RoutingException, ("Feature", featureId, "not found in ", m_country));

  feature->ParseGeometry(FeatureType::BEST_GEOMETRY);

  geometry::Altitudes const * altitudes = nullptr;
  if (m_loadAltitudes)
    altitudes = &(m_altitudeLoader.GetAltitudes(featureId, feature->GetPointsCount()));

  road.Load(*m_vehicleModel, *feature, altitudes, m_attrLoader.m_cityRoads->IsCityRoad(featureId),
            m_attrLoader.m_maxspeeds->GetMaxspeed(featureId));
  m_altitudeLoader.ClearCache();
}

class FileGeometryLoader final : public GeometryLoader
{
public:
  FileGeometryLoader(string const & fileName, shared_ptr<VehicleModelInterface> vehicleModel);

  // GeometryLoader overrides:
  void Load(uint32_t featureId, RoadGeometry & road) override;

private:
  FeaturesVectorTest m_featuresVector;
  CityRoads m_cityRoads;
  Maxspeeds m_maxspeeds;
  shared_ptr<VehicleModelInterface> m_vehicleModel;
};

FileGeometryLoader::FileGeometryLoader(string const & fileName,
                                       shared_ptr<VehicleModelInterface> vehicleModel)
  : m_featuresVector(FilesContainerR(make_unique<FileReader>(fileName)))
  , m_vehicleModel(vehicleModel)
{
  auto const cont = FilesContainerR(make_unique<FileReader>(fileName));

  try
  {
    if (cont.IsExist(CITY_ROADS_FILE_TAG))
      LoadCityRoads(fileName, cont.GetReader(CITY_ROADS_FILE_TAG), m_cityRoads);

    if (cont.IsExist(MAXSPEEDS_FILE_TAG))
      LoadMaxspeeds(cont.GetReader(MAXSPEEDS_FILE_TAG), m_maxspeeds);
  }
  catch (Reader::OpenException const & e)
  {
    LOG(LERROR, ("File", cont.GetFileName(), "Error while reading", CITY_ROADS_FILE_TAG, "or",
                 MAXSPEEDS_FILE_TAG, "section.", e.Msg()));
  }

  CHECK(m_vehicleModel, ());
}

void FileGeometryLoader::Load(uint32_t featureId, RoadGeometry & road)
{
  auto feature = m_featuresVector.GetVector().GetByIndex(featureId);
  CHECK(feature, ());
  feature->ParseGeometry(FeatureType::BEST_GEOMETRY);
  // Note. If FileGeometryLoader is used for generation cross mwm section for bicycle or
  // pedestrian routing |altitudes| should be used here.
  road.Load(*m_vehicleModel, *feature, nullptr /* altitudes */, m_cityRoads.IsCityRoad(featureId),
            m_maxspeeds.GetMaxspeed(featureId));
}
}  // namespace

namespace routing
{
RoadGeometry::RoadGeometry(bool oneWay, double weightSpeedKMpH, double etaSpeedKMpH,
                           Points const & points)
  : m_forwardSpeed{weightSpeedKMpH, etaSpeedKMpH}
  , m_backwardSpeed(m_forwardSpeed)
  , m_isOneWay(oneWay)
  , m_valid(true)
{
  ASSERT_GREATER(weightSpeedKMpH, 0.0, ());
  ASSERT_GREATER(etaSpeedKMpH, 0.0, ());

  m_junctions.reserve(points.size());
  for (auto const & point : points)
    m_junctions.emplace_back(mercator::ToLatLon(point), geometry::kDefaultAltitudeMeters);
}

void RoadGeometry::Load(VehicleModelInterface const & vehicleModel, FeatureType & feature,
                        geometry::Altitudes const * altitudes, bool inCity,
                        Maxspeed const & maxspeed)
{
  CHECK(altitudes == nullptr || altitudes->size() == feature.GetPointsCount(), ());

  m_valid = vehicleModel.IsRoad(feature);
  m_isOneWay = vehicleModel.IsOneWay(feature);
  m_forwardSpeed = vehicleModel.GetSpeed(feature, {true /* forward */, inCity, maxspeed});
  m_backwardSpeed = vehicleModel.GetSpeed(feature, {false /* forward */, inCity, maxspeed});
  m_highwayType = vehicleModel.GetHighwayType(feature);
  m_isPassThroughAllowed = vehicleModel.IsPassThroughAllowed(feature);

  feature::TypesHolder types(feature);
  auto const & optionsClassfier = RoutingOptionsCl

assifier::Instance();
  for (uint32_t type : types)
  {
    if (auto const it = optionsClassfier.Get(type))
      m_routingOptions.Add(*it);
  }

  m_junctions.clear();
  m_junctions.reserve(feature.GetPointsCount());
  for (size_t i = 0; i < feature.GetPointsCount(); ++i)
  {
    m_junctions.emplace_back(mercator::ToLatLon(feature.GetPoint(i)),
                             altitudes ? (*altitudes)[i] : geometry::kDefaultAltitudeMeters);
  }

  if (m_routingOptions.Has(RoutingOptions::Road::Ferry))
  {
    auto const durationHours = feature.GetMetadata(feature::Metadata::FMD_DURATION);
    auto const roadLenKm = GetRoadLengthM() / 1000.0;
    double const durationH = CalcFerryDurationHours(durationHours, roadLenKm);

    CHECK(!base::AlmostEqualAbs(durationH, 0.0, 1e-5), (durationH));

    if (roadLenKm != 0.0)
    {
      m_forwardSpeed = m_backwardSpeed =
          SpeedKMpH(std::min(vehicleModel.GetMaxWeightSpeed(), roadLenKm / durationH));
    }
  }

  if (m_valid && (!m_forwardSpeed.IsValid() || !m_backwardSpeed.IsValid()))
  {
    auto const & id = feature.GetID();
    CHECK(!m_junctions.empty(), ("mwm:", id.GetMwmName(), ", featureId:", id.m_index));
    auto const & begin = m_junctions.front().GetLatLon();
    auto const & end = m_junctions.back().GetLatLon();
    LOG(LERROR,
        ("Invalid speed m_forwardSpeed:", m_forwardSpeed, "m_backwardSpeed:", m_backwardSpeed,
         "mwm:", id.GetMwmName(), ", featureId:", id.m_index, ", begin:", begin, "end:", end));
    m_valid = false;
  }
}

SpeedKMpH const & RoadGeometry::GetSpeed(bool forward) const
{
  return forward ? m_forwardSpeed : m_backwardSpeed;
}

double RoadGeometry::GetRoadLengthM() const
{
  double lenM = 0.0;
  for (size_t i = 1; i < GetPointsCount(); ++i)
  {
    lenM += ms::DistanceOnEarth(m_junctions[i - 1].GetLatLon(), m_junctions[i].GetLatLon());
  }

  return lenM;
}

Geometry::Geometry(unique_ptr<GeometryLoader> loader, size_t roadsCacheSize)
  : m_loader(move(loader))
  , m_featureIdToRoad(make_unique<RoutingFifoCache>(
        roadsCacheSize,
        [this](uint32_t featureId, RoadGeometry & road) { m_loader->Load(featureId, road); }))
{
  CHECK(m_loader, ());
}

RoadGeometry const & Geometry::GetRoad(uint32_t featureId)
{
  ASSERT(m_featureIdToRoad, ());
  ASSERT(m_loader, ());

  return m_featureIdToRoad->GetValue(featureId);
}

unique_ptr<GeometryLoader> GeometryLoader::Create(DataSource const & dataSource,
                                                  MwmSet::MwmHandle const & handle,
                                                  shared_ptr<VehicleModelInterface> vehicleModel,
                                                  AttrLoader && attrLoader, bool loadAltitudes)
{
  CHECK(handle.IsAlive(), ());
  return make_unique<GeometryLoaderImpl>(dataSource, handle, vehicleModel, move(attrLoader),
                                         loadAltitudes);
}

unique_ptr<GeometryLoader> GeometryLoader::CreateFromFile(
    string const & fileName, shared_ptr<VehicleModelInterface> vehicleModel)
{
  return make_unique<FileGeometryLoader>(fileName, vehicleModel);
}
}  // namespace routing


#include "constant_folding.hpp"
#include "ngraph/builder/split.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/variadic_split.hpp"
#include "ngraph/validation_util.hpp"

using namespace std;
using namespace ngraph;

{
    auto data_label = make_shared<pattern::op::Label>(
        element::f32, Shape{2, 3, 4}, pattern::has_class<op::v0::Constant>());
    auto axis_label = make_shared<pattern::op::Label>(
        element::i64, Shape{}, pattern::has_class<op::v0::Constant>());
    auto lengths_label = make_shared<pattern::op::Label>(
        element::i64, Shape{2}, pattern::has_class<op::v0::Constant>());
    auto variadic_split_pattern =
        make_shared<op::v1::VariadicSplit>(data_label, axis_label, lengths_label);

    auto constant_variadic_split_callback = [this, data_label, axis_label, lengths_label](
                                                pattern::Matcher& m) {
        NGRAPH_DEBUG << "In callback for constant_variadic_split_callback against node = "
                     << m.get_match_root()->get_name();
        auto pattern_map = m.get_pattern_map();

        const auto data_node = static_pointer_cast<op::v0::Constant>(pattern_map[data_label]);
        const auto axis_node = static_pointer_cast<op::v0::Constant>(pattern_map[axis_label]);
        const auto lengths_node = static_pointer_cast<op::v0::Constant>(pattern_map[lengths_label]);
        const auto variadic_split = m.get_match_root_as<op::v1::VariadicSplit>();
        NGRAPH_CHECK(variadic_split,
                     "match root node ",
                     *m.get_match_root(),
                     " not of type `op::v1::VariadicSplit`");

        const auto axis_val = axis_node->cast_vector<int64_t>()[0];
        const auto norm_axis_val = ngraph::normalize_axis(
            variadic_split.get(), axis_val, data_node->get_output_partial_shape(0).rank());
        auto split_lengths = lengths_node->cast_vector<int64_t>();

        // Adjust split lengths in case of negatives
        size_t sum_of_splits = 0;
        int64_t negative_one = -1;
        for (size_t i = 0; i < split_lengths.size(); i++)
        {
            if (split_lengths[i] == -1)
            {
                negative_one = i;
            }
            else
            {
                sum_of_splits += split_lengths[i];
            }
        }

        if (negative_one > 0)
        {
            split_lengths[negative_one] =
                static_cast<size_t>(data_node->get_output_shape(0)[norm_axis_val]) - sum_of_splits;
        }

        const auto slices = builder::split(
            data_node, vector<size_t>(split_lengths.begin(), split_lengths.end()), norm_axis_val);

        for (size_t i = 0; i < variadic_split->get_output_size(); i++)
        {
            for (auto& input : variadic_split->output(i).get_target_inputs())
            {
                input.replace_source_output(slices[i]);
            }
        }
        variadic_split->outputs().clear();
        construct_constant_slice();

        return true;
    };
    auto variadic_split_matcher = make_shared<pattern::Matcher>(
        variadic_split_pattern, "ConstantFolding.ConstantVariadicSplit");
    this->add_matcher(variadic_split_matcher,
                      constant_variadic_split_callback,
                      PassProperty::CHANGE_DYNAMIC_STATE);
}


{
    auto data_label = make_shared<pattern::op::Label>(
        element::f32, Shape{2, 3, 4}, pattern::has_class<op::v0::Constant>());
    auto axis_label = make_shared<pattern::op::Label>(
        element::i64, Shape{}, pattern::has_class<op::v0::Constant>());

#include "constant_folding.hpp"
#include "ngraph/builder/split.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/variadic_split.hpp"
#include "ngraph/validation_util.hpp"

using namespace std;
using namespace ngraph;

void pass::ConstantFolding::construct_constant_variadic_split()

    auto lengths_label = make_shared<pattern::op::Label>(
        element::i64, Shape{2}, pattern::has_class<op::v0::Constant>());
    auto variadic_split_pattern =
        make_shared<op::v1::VariadicSplit>(data_label, axis_label, lengths_label);

    auto constant_variadic_split_callback = [this, data_label, axis_label, lengths_label](
                                                pattern::Matcher& m) {
        NGRAPH_DEBUG << "In callback for constant_variadic_split_callback against node = "
                     << m.get_match_root()->get_name();
        auto pattern_map = m.get_pattern_map();

        const auto data_node = static_pointer_cast<op::v0::Constant>(pattern_map[data_label]);
        const auto axis_node = static_pointer_cast<op::v0::Constant>(pattern_map[axis_label]);
        const auto lengths_node = static_pointer_cast<op::v0::Constant>(pattern_map[lengths_label]);
        const auto variadic_split = m.get_match_root_as<op::v1::VariadicSplit>();
        NGRAPH_CHECK(variadic_split,
                     "match root node ",
                     *m.get_match_root(),
                     " not of type `op::v1::VariadicSplit`");

        const auto axis_val = axis_node->cast_vector<int64_t>()[0];
        const auto norm_axis_val = ngraph::normalize_axis(
            variadic_split.get(), axis_val, data_node->get_output_partial_shape(0).rank());
        auto split_lengths = lengths_node->cast_vector<int64_t>();

        // Adjust split lengths in case of negatives
        size_t sum_of_splits = 0;
        int64_t negative_one = -1;
        for (size_t i = 0; i < split_lengths.size(); i++)
        {
            if (split_lengths[i] == -1)
            {
                negative_one = i;
            }
            else
            {
                sum_of_splits += split_lengths[i];
            }
        }

        if (negative_one > 0)
        {
            split_lengths[negative_one] =
                static_cast<size_t>(data_node->get_output_shape(0)[norm_axis_val]) - sum_of_splits;
        }

        const auto slices = builder::split(
            data_node, vector<size_t>(split_lengths.begin(), split_lengths.end()), norm_axis_val);

        for (size_t i = 0; i < variadic_split->get_output_size(); i++)
        {
            for (auto& input : variadic_split->output(i).get_target_inputs())
            {
                input.replace_source_output(slices[i]);
            }
        }
        variadic_split->outputs().clear();
        construct_constant_slice();

        return true;
    };
    auto variadic_split_matcher = make_shared<pattern::Matcher>(
        variadic_split_pattern, "ConstantFolding.ConstantVariadicSplit");
    this->add_matcher(variadic_split_matcher,
                      constant_variadic_split_callback,
                      PassProperty::CHANGE_DYNAMIC_STATE);
}


auto lengths_label = make_shared<pattern::op::Label>(
        element::i64, Shape{2}, pattern::has_class<op::v0::Constant>());
    auto variadic_split_pattern =
        make_shared<op::v1::VariadicSplit>(data_label, axis_label, lengths_label);

    auto constant_variadic_split_callback = [this, data_label, axis_label, lengths_label](
                                                pattern::Matcher& m) {
        NGRAPH_DEBUG << "In callback for constant_variadic_split_callback against node = "
                     << m.get_match_root()->get_name();
        auto pattern_map = m.get_pattern_map();

        const auto data_node = static_pointer_cast<op::v0::Constant>(pattern_map[data_label]);
        const auto axis_node = static_pointer_cast<op::v0::Constant>(pattern_map[axis_label]);
        const auto lengths_node = static_pointer_cast<op::v0::Constant>(pattern_map[lengths_label]);
        const auto variadic_split = m.get_match_root_as<op::v1::VariadicSplit>();
        NGRAPH_CHECK(variadic_split,
                     "match root node ",
                     *m.get_match_root(),
                     " not of type `op::v1::VariadicSplit`");

        const auto axis_val = axis_node->cast_vector<int64_t>()[0];
        const auto norm_axis_val = ngraph::normalize_axis(
            variadic_split.get(), axis_val, data_node->get_output_partial_shape(0).rank());
        auto split_lengths = lengths_node->cast_vector<int64_t>();

        // Adjust split lengths in case of negatives
        size_t sum_of_splits = 0;
        int64_t negative_one = -1;
        for (size_t i = 0; i < split_lengths.size(); i++)
        {
            if (split_lengths[i] == -1)
            {
                negative_one = i;
            }
            else
            {
                sum_of_splits += split_lengths[i];
            }
        }

        if (negative_one > 0)
        {
            split_lengths[negative_one] =
                static_cast<size_t>(data_node->get_output_shape(0)[norm_axis_val]) - sum_of_splits;
        }

        const auto slices = builder::split(
            data_node, vector<size_t>(split_lengths.begin(), split_lengths.end()), norm_axis_val);

        for (size_t i = 0; i < variadic_split->get_output_size(); i++)
        {
            for (auto& input : variadic_split->output(i).get_target_inputs())
            {
                input.replace_source_output(slices[i]);
            }
        }
        variadic_split->outputs().clear();
        construct_constant_slice();

        return true;
    };

#include "constant_folding.hpp"
#include "ngraph/builder/split.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/variadic_split.hpp"
#include "ngraph/validation_util.hpp"

using namespace std;
using namespace ngraph;

void pass::ConstantFolding::construct_constant_variadic_split()
{
    auto data_label = make_shared<pattern::op::Label>(
        element::f32, Shape{2, 3, 4}, pattern::has_class<op::v0::Constant>());
    auto axis_label = make_shared<pattern::op::Label>(
        element::i64, Shape{}, pattern::has_class<op::v0::Constant>());

    auto variadic_split_matcher = make_shared<pattern::Matcher>(
        variadic_split_pattern, "ConstantFolding.ConstantVariadicSplit");
    this->add_matcher(variadic_split_matcher,
                      constant_variadic_split_callback,
                      PassProperty::CHANGE_DYNAMIC_STATE);
}


auto variadic_split_matcher = make_shared<pattern::Matcher>(
        variadic_split_pattern, "ConstantFolding.ConstantVariadicSplit");
    this->add_matcher(variadic_split_matcher,
                      constant_variadic_split_callback,
                      PassProperty::CHANGE_DYNAMIC_STATE);
}

#include "constant_folding.hpp"
#include "ngraph/builder/split.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/variadic_split.hpp"
#include "ngraph/validation_util.hpp"

using namespace std;
using namespace ngraph;

void pass::ConstantFolding::construct_constant_variadic_split()
{
    auto data_label = make_shared<pattern::op::Label>(
        element::f32, Shape{2, 3, 4}, pattern::has_class<op::v0::Constant>());
    auto axis_label = make_shared<pattern::op::Label>(
        element::i64, Shape{}, pattern::has_class<op::v0::Constant>());
    auto lengths_label = make_shared<pattern::op::Label>(
        element::i64, Shape{2}, pattern::has_class<op::v0::Constant>());
    auto variadic_split_pattern =
        make_shared<op::v1::VariadicSplit>(data_label, axis_label, lengths_label);

    auto constant_variadic_split_callback = [this, data_label, axis_label, lengths_label](
                                                pattern::Matcher& m) {
        NGRAPH_DEBUG << "In callback for constant_variadic_split_callback against node = "
                     << m.get_match_root()->get_name();
        auto pattern_map = m.get_pattern_map();

        const auto data_node = static_pointer_cast<op::v0::Constant>(pattern_map[data_label]);
        const auto axis_node = static_pointer_cast<op::v0::Constant>(pattern_map[axis_label]);
        const auto lengths_node = static_pointer_cast<op::v0::Constant>(pattern_map[lengths_label]);
        const auto variadic_split = m.get_match_root_as<op::v1::VariadicSplit>();
        NGRAPH_CHECK(variadic_split,
                     "match root node ",
                     *m.get_match_root(),
                     " not of type `op::v1::VariadicSplit`");

        const auto axis_val = axis_node->cast_vector<int64_t>()[0];
        const auto norm_axis_val = ngraph::normalize_axis(
            variadic_split.get(), axis_val, data_node->get_output_partial_shape(0).rank());
        auto split_lengths = lengths_node->cast_vector<int64_t>();

        // Adjust split lengths in case of negatives
        size_t sum_of_splits = 0;
        int64_t negative_one = -1;
        for (size_t i = 0; i < split_lengths.size(); i++)
        {
            if (split_lengths[i] == -1)
            {
                negative_one = i;
            }
            else
            {
                sum_of_splits += split_lengths[i];
            }
        }

        if (negative_one > 0)
        {
            split_lengths[negative_one] =
                static_cast<size_t>(data_node->get_output_shape(0)[norm_axis_val]) - sum_of_splits;
        }

        const auto slices = builder::split(
            data_node, vector<size_t>(split_lengths.begin(), split_lengths.end()), norm_axis_val);

        for (size_t i = 0; i < variadic_split->get_output_size(); i++)
        {
            for (auto& input : variadic_split->output(i).get_target_inputs())
            {
                input.replace_source_output(slices[i]);
            }
        }
        variadic_split->outputs().clear();
        construct_constant_slice();

        return true;
    };


if (negative_one > 0)
        {
            split_lengths[negative_one] =
                static_cast<size_t>(data_node->get_output_shape(0)[norm_axis_val]) - sum_of_splits;
        }

#include "constant_folding.hpp"
#include "ngraph/builder/split.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/variadic_split.hpp"
#include "ngraph/validation_util.hpp"

using namespace std;
using namespace ngraph;

void pass::ConstantFolding::construct_constant_variadic_split()
{
    auto data_label = make_shared<pattern::op::Label>(
        element::f32, Shape{2, 3, 4}, pattern::has_class<op::v0::Constant>());
    auto axis_label = make_shared<pattern::op::Label>(
        element::i64, Shape{}, pattern::has_class<op::v0::Constant>());
    auto lengths_label = make_shared<pattern::op::Label>(
        element::i64, Shape{2}, pattern::has_class<op::v0::Constant>());
    auto variadic_split_pattern =
        make_shared<op::v1::VariadicSplit>(data_label, axis_label, lengths_label);

    auto constant_variadic_split_callback = [this, data_label, axis_label, lengths_label](
                                                pattern::Matcher& m) {
        NGRAPH_DEBUG << "In callback for constant_variadic_split_callback against node = "
                     << m.get_match_root()->get_name();
        auto pattern_map = m.get_pattern_map();

        const auto data_node = static_pointer_cast<op::v0::Constant>(pattern_map[data_label]);
        const auto axis_node = static_pointer_cast<op::v0::Constant>(pattern_map[axis_label]);
        const auto lengths_node = static_pointer_cast<op::v0::Constant>(pattern_map[lengths_label]);
        const auto variadic_split = m.get_match_root_as<op::v1::VariadicSplit>();
        NGRAPH_CHECK(variadic_split,
                     "match root node ",
                     *m.get_match_root(),
                     " not of type `op::v1::VariadicSplit`");

        const auto axis_val = axis_node->cast_vector<int64_t>()[0];
        const auto norm_axis_val = ngraph::normalize_axis(
            variadic_split.get(), axis_val, data_node->get_output_partial_shape(0).rank());
        auto split_lengths = lengths_node->cast_vector<int64_t>();

        // Adjust split lengths in case of negatives
        size_t sum_of_splits = 0;
        int64_t negative_one = -1;
        for (size_t i = 0; i < split_lengths.size(); i++)
        {
            if (split_lengths[i] == -1)
            {
                negative_one = i;
            }
            else
            {
                sum_of_splits += split_lengths[i];
            }
        }

        const auto slices = builder::split(
            data_node, vector<size_t>(split_lengths.begin(), split_lengths.end()), norm_axis_val);

        for (size_t i = 0; i < variadic_split->get_output_size(); i++)
        {
            for (auto& input : variadic_split->output(i).get_target_inputs())
            {
                input.replace_source_output(slices[i]);
            }
        }
        variadic_split->outputs().clear();
        construct_constant_slice();

        return true;
    };
    auto variadic_split_matcher = make_shared<pattern::Matcher>(
        variadic_split_pattern, "ConstantFolding.ConstantVariadicSplit");
    this->add_matcher(variadic_split_matcher,
                      constant_variadic_split_callback,
                      PassProperty::CHANGE_DYNAMIC_STATE);
}


for (size_t i = 0; i < variadic_split->get_output_size(); i++)
        {
            for (auto& input : variadic_split->output(i).get_target_inputs())
            {
                input.replace_source_output(slices[i]);
            }
        }

#include "constant_folding.hpp"
#include "ngraph/builder/split.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/variadic_split.hpp"
#include "ngraph/validation_util.hpp"

using namespace std;
using namespace ngraph;

void pass::ConstantFolding::construct_constant_variadic_split()
{
    auto data_label = make_shared<pattern::op::Label>(
        element::f32, Shape{2, 3, 4}, pattern::has_class<op::v0::Constant>());
    auto axis_label = make_shared<pattern::op::Label>(
        element::i64, Shape{}, pattern::has_class<op::v0::Constant>());
    auto lengths_label = make_shared<pattern::op::Label>(
        element::i64, Shape{2}, pattern::has_class<op::v0::Constant>());
    auto variadic_split_pattern =
        make_shared<op::v1::VariadicSplit>(data_label, axis_label, lengths_label);

    auto constant_variadic_split_callback = [this, data_label, axis_label, lengths_label](
                                                pattern::Matcher& m) {
        NGRAPH_DEBUG << "In callback for constant_variadic_split_callback against node = "
                     << m.get_match_root()->get_name();
        auto pattern_map = m.get_pattern_map();

        const auto data_node = static_pointer_cast<op::v0::Constant>(pattern_map[data_label]);
        const auto axis_node = static_pointer_cast<op::v0::Constant>(pattern_map[axis_label]);
        const auto lengths_node = static_pointer_cast<op::v0::Constant>(pattern_map[lengths_label]);
        const auto variadic_split = m.get_match_root_as<op::v1::VariadicSplit>();
        NGRAPH_CHECK(variadic_split,
                     "match root node ",
                     *m.get_match_root(),
                     " not of type `op::v1::VariadicSplit`");

        const auto axis_val = axis_node->cast_vector<int64_t>()[0];
        const auto norm_axis_val = ngraph::normalize_axis(
            variadic_split.get(), axis_val, data_node->get_output_partial_shape(0).rank());
        auto split_lengths = lengths_node->cast_vector<int64_t>();

        // Adjust split lengths in case of negatives
        size_t sum_of_splits = 0;
        int64_t negative_one = -1;
        for (size_t i = 0; i < split_lengths.size(); i++)
        {
            if (split_lengths[i] == -1)
            {
                negative_one = i;
            }
            else
            {
                sum_of_splits += split_lengths[i];
            }
        }

        if (negative_one > 0)
        {
            split_lengths[negative_one] =
                static_cast<size_t>(data_node->get_output_shape(0)[norm_axis_val]) - sum_of_splits;
        }

        const auto slices = builder::split(
            data_node, vector<size_t>(split_lengths.begin(), split_lengths.end()), norm_axis_val);

        variadic_split->outputs().clear();
        construct_constant_slice();

        return true;
    };
    auto variadic_split_matcher = make_shared<pattern::Matcher>(
        variadic_split_pattern, "ConstantFolding.ConstantVariadicSplit");
    this->add_matcher(variadic_split_matcher,
                      constant_variadic_split_callback,
                      PassProperty::CHANGE_DYNAMIC_STATE);
}


#include "constant_folding.hpp"
#include "ngraph/builder/split.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/variadic_split.hpp"
#include "ngraph/validation_util.hpp"

using namespace std;
using namespace ngraph;

void pass::ConstantFolding::construct_constant_variadic_split()
{
    auto data_label = make_shared<pattern::op::Label>(
        element::f32, Shape{2, 3, 4}, pattern::has_class<op::v0::Constant>());
    auto axis_label = make_shared<pattern::op::Label>(
        element::i64, Shape{}, pattern::has_class<op::v0::Constant>());
    auto lengths_label = make_shared<pattern::op::Label>(
        element::i64, Shape{2}, pattern::has_class<op::v0::Constant>());
    auto variadic_split_pattern =
        make_shared<op::v1::VariadicSplit>(data_label, axis_label, lengths_label);

    auto constant_variadic_split_callback = [this, data_label, axis_label, lengths_label](
                                                pattern::Matcher& m) {
        NGRAPH_DEBUG << "In callback for constant_variadic_split_callback against node = "
                     << m.get_match_root()->get_name();
        auto pattern_map = m.get_pattern_map();

        const auto data_node = static_pointer_cast<op::v0::Constant>(pattern_map[data_label]);
        const auto axis_node = static_pointer_cast<op::v0::Constant>

(pattern_map[axis_label]);
        const auto lengths_node = static_pointer_cast<op::v0::Constant>(pattern_map[lengths_label]);
        const auto variadic_split = m.get_match_root_as<op::v1::VariadicSplit>();
        NGRAPH_CHECK(variadic_split,
                     "match root node ",
                     *m.get_match_root(),
                     " not of type `op::v1::VariadicSplit`");

        const auto axis_val = axis_node->cast_vector<int64_t>()[0];
        const auto norm_axis_val = ngraph::normalize_axis(
            variadic_split.get(), axis_val, data_node->get_output_partial_shape(0).rank());
        auto split_lengths = lengths_node->cast_vector<int64_t>();

        // Adjust split lengths in case of negatives
        size_t sum_of_splits = 0;
        int64_t negative_one = -1;
        for (size_t i = 0; i < split_lengths.size(); i++)
        {
            if (split_lengths[i] == -1)
            {
                negative_one = i;
            }
            else
            {
                sum_of_splits += split_lengths[i];
            }
        }

        if (negative_one > 0)
        {
            split_lengths[negative_one] =
                static_cast<size_t>(data_node->get_output_shape(0)[norm_axis_val]) - sum_of_splits;
        }

        const auto slices = builder::split(
            data_node, vector<size_t>(split_lengths.begin(), split_lengths.end()), norm_axis_val);

        for (size_t i = 0; i < variadic_split->get_output_size(); i++)
        {
            for (auto& input : variadic_split->output(i).get_target_inputs())
            {
                input.replace_source_output(slices[i]);
            }
        }
        variadic_split->outputs().clear();
        construct_constant_slice();

        return true;
    };
    auto variadic_split_matcher = make_shared<pattern::Matcher>(
        variadic_split_pattern, "ConstantFolding.ConstantVariadicSplit");
    this->add_matcher(variadic_split_matcher,
                      constant_variadic_split_callback,
                      PassProperty::CHANGE_DYNAMIC_STATE);
}