Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding json conversion and unit test for ProtoAxis #4045

Merged
merged 10 commits into from
Jan 24, 2025
1 change: 1 addition & 0 deletions Plugins/Json/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ add_library(
src/MaterialJsonConverter.cpp
src/PortalJsonConverter.cpp
src/ProtoDetectorJsonConverter.cpp
src/ProtoAxisJsonConverter.cpp
src/SurfaceBoundsJsonConverter.cpp
src/SurfaceJsonConverter.cpp
src/UtilitiesJsonConverter.cpp
Expand Down
34 changes: 34 additions & 0 deletions Plugins/Json/include/Acts/Plugins/Json/ProtoAxisJsonConverter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This file is part of the ACTS project.
//
// Copyright (C) 2016 CERN for the benefit of the ACTS project
//
// 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 https://mozilla.org/MPL/2.0/.

#pragma once

#include "Acts/Plugins/Json/ActsJson.hpp"
#include "Acts/Utilities/ProtoAxis.hpp"

#include <nlohmann/json.hpp>

/// Custom Json encoder/decoders. Naming is mandated by nlohmann::json and thus
/// can not match our naming guidelines.
///
/// This uses a custom API and nomenclature as it would
/// otherwise require the ProtoAxis to have a default
/// constructor which is deleted
namespace Acts::ProtoAxisJsonConverter {

/// Write the ProtoAxis to a json object
///
/// @param pa the proto axis to be written out
nlohmann::json toJson(const ProtoAxis& pa);

/// Create a ProtoAxis from a json object
///
/// @param j the json object to be read from
Acts::ProtoAxis fromJson(const nlohmann::json& j);

} // namespace Acts::ProtoAxisJsonConverter
53 changes: 53 additions & 0 deletions Plugins/Json/src/ProtoAxisJsonConverter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// This file is part of the ACTS project.
//
// Copyright (C) 2016 CERN for the benefit of the ACTS project
//
// 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 https://mozilla.org/MPL/2.0/.

#include "Acts/Plugins/Json/ProtoAxisJsonConverter.hpp"

#include "Acts/Plugins/Json/GridJsonConverter.hpp"
#include "Acts/Plugins/Json/UtilitiesJsonConverter.hpp"
#include "Acts/Utilities/AxisDefinitions.hpp"

nlohmann::json Acts::ProtoAxisJsonConverter::toJson(const Acts::ProtoAxis& pa) {
nlohmann::json j;
j["axis_dir"] = pa.getAxisDirection();
j["axis"] = AxisJsonConverter::toJson(pa.getAxis());
j["autorange"] = pa.isAutorange();
return j;
}

Acts::ProtoAxis Acts::ProtoAxisJsonConverter::fromJson(
const nlohmann::json& j) {
auto axisDir = j.at("axis_dir").get<Acts::AxisDirection>();
auto axisBoundaryType =
j.at("axis").at("boundary_type").get<Acts::AxisBoundaryType>();
if (auto axisType = j.at("axis").at("type").get<Acts::AxisType>();
axisType == AxisType::Equidistant) {
auto nbins = j.at("axis").at("bins").get<std::size_t>();
if (nbins == 0) {
throw std::invalid_argument("Number of bins must be positive");
}

if (j.at("autorange").get<bool>()) {
return ProtoAxis(axisDir, axisBoundaryType, nbins);
}
auto min = j.at("axis").at("range").at(0).get<double>();
auto max = j.at("axis").at("range").at(1).get<double>();
if (min >= max) {
throw std::invalid_argument("Invalid range: min must be less than max");
}
return ProtoAxis(axisDir, axisBoundaryType, min, max, nbins);
}
auto binEdges = j.at("axis").at("boundaries").get<std::vector<double>>();
if (binEdges.size() < 2) {
throw std::invalid_argument("At least two bin edges required");
}
if (!std::ranges::is_sorted(binEdges)) {
throw std::invalid_argument("Bin edges must be sorted in ascending order");
}
return ProtoAxis(axisDir, axisBoundaryType, binEdges);
}
1 change: 1 addition & 0 deletions Tests/UnitTests/Plugins/Json/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_unittest(GridJsonConverter GridJsonConverterTests.cpp)
add_unittest(MaterialJsonConverter MaterialJsonConverterTests.cpp)
add_unittest(MaterialMapJsonConverter MaterialMapJsonConverterTests.cpp)
add_unittest(PortalJsonConverter PortalJsonConverterTests.cpp)
add_unittest(ProtoAxisJsonConverter ProtoAxisJsonConverterTests.cpp)
add_unittest(ProtoDetectorJsonConverter ProtoDetectorJsonConverterTests.cpp)
add_unittest(UtilitiesJsonConverter UtilitiesJsonConverterTests.cpp)
add_unittest(SurfaceBoundsJsonConverter SurfaceBoundsJsonConverterTests.cpp)
Expand Down
155 changes: 155 additions & 0 deletions Tests/UnitTests/Plugins/Json/ProtoAxisJsonConverterTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// This file is part of the ACTS project.
//
// Copyright (C) 2016 CERN for the benefit of the ACTS project
//
// 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 https://mozilla.org/MPL/2.0/.

#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>

#include "Acts/Plugins/Json/ActsJson.hpp"
#include "Acts/Plugins/Json/ProtoAxisJsonConverter.hpp"
#include "Acts/Tests/CommonHelpers/FloatComparisons.hpp"
#include "Acts/Utilities/Axis.hpp"
#include "Acts/Utilities/AxisDefinitions.hpp"
#include "Acts/Utilities/ProtoAxis.hpp"

BOOST_AUTO_TEST_SUITE(ProtoAxisJsonConversion)

BOOST_AUTO_TEST_CASE(EquidistantProtoAxisJsonConversion) {
using enum Acts::AxisBoundaryType;
using enum Acts::AxisDirection;
using enum Acts::AxisType;

// Bound, equidistant axis
Acts::ProtoAxis epab(AxisX, Bound, 0.0, 1.0, 10);

nlohmann::json jProtoAxis = Acts::ProtoAxisJsonConverter::toJson(epab);

BOOST_CHECK(jProtoAxis.contains("axis"));
BOOST_CHECK(jProtoAxis.contains("axis_dir"));
BOOST_CHECK(jProtoAxis.contains("autorange"));

Acts::ProtoAxis epabRead = Acts::ProtoAxisJsonConverter::fromJson(jProtoAxis);

BOOST_CHECK_EQUAL(epabRead.getAxisDirection(), epab.getAxisDirection());
BOOST_CHECK_EQUAL(epabRead.getAxis(), epab.getAxis());
BOOST_CHECK_EQUAL(epabRead.isAutorange(), epab.isAutorange());
BOOST_CHECK_EQUAL(epabRead.toString(), epab.toString());
}

BOOST_AUTO_TEST_CASE(AutorangeProtoAxisJsonConversion) {
using enum Acts::AxisBoundaryType;
using enum Acts::AxisDirection;
using enum Acts::AxisType;

// Bound, equidistant axis, autorange
Acts::ProtoAxis epa(AxisX, Bound, 10);

nlohmann::json jProtoAxis = Acts::ProtoAxisJsonConverter::toJson(epa);

BOOST_CHECK(jProtoAxis.contains("axis"));
BOOST_CHECK(jProtoAxis.contains("axis_dir"));
BOOST_CHECK(jProtoAxis.contains("autorange"));

Acts::ProtoAxis epaRead = Acts::ProtoAxisJsonConverter::fromJson(jProtoAxis);

BOOST_CHECK_EQUAL(epaRead.getAxisDirection(), epa.getAxisDirection());
BOOST_CHECK_EQUAL(epaRead.getAxis(), epa.getAxis());
BOOST_CHECK_EQUAL(epaRead.isAutorange(), epa.isAutorange());
BOOST_CHECK_EQUAL(epaRead.toString(), epa.toString());
}

BOOST_AUTO_TEST_CASE(VariableProtoAxisJsonConversion) {
using enum Acts::AxisBoundaryType;
using enum Acts::AxisDirection;
using enum Acts::AxisType;

// Bound, variable axis
Acts::ProtoAxis vpab(AxisX, Bound, {0.0, 1.0, 10});

nlohmann::json jProtoAxis = Acts::ProtoAxisJsonConverter::toJson(vpab);
BOOST_CHECK(jProtoAxis.contains("axis"));
BOOST_CHECK(jProtoAxis.contains("axis_dir"));
BOOST_CHECK(jProtoAxis.contains("autorange"));

Acts::ProtoAxis vpabRead = Acts::ProtoAxisJsonConverter::fromJson(jProtoAxis);

BOOST_CHECK_EQUAL(vpabRead.getAxisDirection(), vpab.getAxisDirection());
BOOST_CHECK_EQUAL(vpabRead.getAxis(), vpab.getAxis());
BOOST_CHECK_EQUAL(vpabRead.isAutorange(), vpab.isAutorange());
BOOST_CHECK_EQUAL(vpabRead.toString(), vpab.toString());
}

BOOST_AUTO_TEST_CASE(InvalidAndValidInputJson) {
// valid eq axis input
nlohmann::json jValidEqAxis = {{"bins", 10},
{"boundary_type", "Bound"},
{"range", std::array<double, 2>{0.0, 1.0}},
{"type", "Equidistant"}};

// Valid input first
nlohmann::json jValidEq = {
{"axis", jValidEqAxis}, {"axis_dir", "AxisX"}, {"autorange", false}};

BOOST_CHECK_NO_THROW(Acts::ProtoAxisJsonConverter::fromJson(jValidEq));

// Invalid input - zero bins
nlohmann::json jInvalidEqAxis = jValidEqAxis;
jInvalidEqAxis["bins"] = 0;

nlohmann::json jInvalidEq = {
{"axis", jInvalidEqAxis}, {"axis_dir", "AxisX"}, {"autorange", false}};

BOOST_CHECK_THROW(Acts::ProtoAxisJsonConverter::fromJson(jInvalidEq),
std::invalid_argument);

// Invalid input - auto range without bins
jInvalidEq = {
{"axis", jInvalidEqAxis}, {"axis_dir", "AxisX"}, {"autorange", true}};
BOOST_CHECK_THROW(Acts::ProtoAxisJsonConverter::fromJson(jInvalidEq),
std::invalid_argument);

// Invalid input - min >= max
jInvalidEqAxis = jValidEqAxis;
jInvalidEqAxis["range"] = std::array<double, 2>{1.0, 0.0};

jInvalidEq = {
{"axis", jInvalidEqAxis}, {"axis_dir", "AxisX"}, {"autorange", false}};

BOOST_CHECK_THROW(Acts::ProtoAxisJsonConverter::fromJson(jInvalidEq),
std::invalid_argument);

nlohmann::json jValidVarAxis = {
{"boundary_type", "Bound"},
{"boundaries", std::vector<double>{0.0, 0.25, 0.75, 1.0}},
{"type", "Variable"}};

// Valid input first
nlohmann::json jValidVar = {
{"axis", jValidVarAxis}, {"axis_dir", "AxisX"}, {"autorange", false}};
BOOST_CHECK_NO_THROW(Acts::ProtoAxisJsonConverter::fromJson(jValidVar));

// Invalid input - less than two edges
nlohmann::json jInvalidVarAxis = jValidVarAxis;
jInvalidVarAxis["boundaries"] = std::vector<double>{0.0};

nlohmann::json jInvalidVar = {
{"axis", jInvalidVarAxis}, {"axis_dir", "AxisX"}, {"autorange", false}};
BOOST_CHECK_THROW(Acts::ProtoAxisJsonConverter::fromJson(jInvalidVar),
std::invalid_argument);

// Invalid input - non-increasing edges
jInvalidVarAxis = jValidVarAxis;
jInvalidVarAxis["boundaries"] = std::vector<double>{0.0, 0.75, 0.25, 1.0};

jInvalidVar = {
{"axis", jInvalidVarAxis}, {"axis_dir", "AxisX"}, {"autorange", false}};

BOOST_CHECK_THROW(Acts::ProtoAxisJsonConverter::fromJson(jInvalidVar),
std::invalid_argument);
}

BOOST_AUTO_TEST_SUITE_END()
Loading