IAP GITLAB

Commit 01efa4c1 authored by Ralf Ulrich's avatar Ralf Ulrich

implemented Max's suggestions

parent 54e09312
......@@ -240,12 +240,9 @@ add_subdirectory (modules/urqmd)
add_subdirectory (modules/conex)
#
#+++++++++++++++++++++++++++++++
# tests
# unit testing
#
add_subdirectory (tests/framework)
add_subdirectory (tests/media)
add_subdirectory (tests/stack)
add_subdirectory (tests/modules)
add_subdirectory (tests)
#
#+++++++++++++++++++++++++++++++
# examples
......
......@@ -57,8 +57,8 @@ function (CORSIKA_ADD_TEST)
endif ()
add_executable (${name} ${sources})
target_link_libraries (${name} CORSIKA8 Catch2)
target_compile_options (${name} PRIVATE -g) # do not skip asserts
target_link_libraries (${name} CORSIKA8 Catch2 CorsikaTesting)
target_compile_options (${name} PRIVATE -g) # do not skip asserts
target_include_directories (${name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
file (MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/test_outputs/)
if (CORSIKA_SANITIZERS_ENABLED)
......@@ -77,7 +77,7 @@ endfunction (CORSIKA_ADD_TEST)
# Examples can be globally executed by 'make run_examples'
#
# 1) Simple use:
# Pass the name of the test.cc file as the first
# Pass the name of the source.cc file as the first
# argument, without the ".cc" extention.
#
# Example: CORSIKA_REGISTER_EXAMPLE (doSomething)
......@@ -85,12 +85,12 @@ endfunction (CORSIKA_ADD_TEST)
# The TARGET doSomething must already exists,
# i.e. typically via add_executable (doSomething src.cc).
#
# Example: CORSIKA_ADD_EXAMPLE (testSomething
# Example: CORSIKA_ADD_EXAMPLE (example_one
# RUN_OPTION "extra command line options"
# )
#
# In all cases, you can further customize the target with
# target_link_libraries(testSomething ...) and so on.
# target_link_libraries(example_one ...) and so on.
#
function (CORSIKA_REGISTER_EXAMPLE)
cmake_parse_arguments (PARSE_ARGV 1 C8_EXAMPLE "" "" "RUN_OPTIONS")
......
......@@ -34,45 +34,41 @@ namespace corsika {
}
/// find transformation between two CS, using most optimal common base
inline EigenTransform get_transformation(CoordinateSystemPtr const& pFrom,
CoordinateSystemPtr const& pTo) {
CoordinateSystemPtr a{pFrom};
CoordinateSystemPtr b{pTo};
CoordinateSystemPtr commonBase{nullptr};
inline EigenTransform get_transformation(CoordinateSystem const& pFrom,
CoordinateSystem const& pTo) {
CoordinateSystem const* a{&pFrom};
CoordinateSystem const* b{&pTo};
while (a != b && b) {
// traverse pFrom
a = pFrom;
a = &pFrom;
while (a != b && a) {
a = a->getReferenceCS();
a = a->getReferenceCS().get();
}
if (a == b) break;
b = b->getReferenceCS();
b = b->getReferenceCS().get();
}
if (a == b && a) {
commonBase = a;
} else {
if (a!=b || a == nullptr) {
throw std::runtime_error("no connection between coordinate systems found!");
}
CoordinateSystem const* commonBase = a;
CoordinateSystem const* p = &pFrom;
EigenTransform t = EigenTransform::Identity();
CoordinateSystemPtr p = pFrom;
while ((*p) != (*commonBase)) {
t = p->getTransform() * t;
p = p->getReferenceCS();
p = p->getReferenceCS().get();
}
p = pTo;
p = &pTo;
while (*p != *commonBase) {
t = t * p->getTransform().inverse(Eigen::TransformTraits::Isometry);
p = p->getReferenceCS();
p = p->getReferenceCS().get();
}
return t;
......
......@@ -31,7 +31,7 @@ namespace corsika {
return BaseVector<length_d>::getQuantityVector().getX();
} else {
return QuantityVector<length_d>(
get_transformation(cs, pCS) *
get_transformation(*cs.get(), *pCS.get()) *
BaseVector<length_d>::getQuantityVector().eigenVector_)
.getX();
}
......@@ -43,7 +43,7 @@ namespace corsika {
return BaseVector<length_d>::getQuantityVector().getY();
} else {
return QuantityVector<length_d>(
get_transformation(cs, pCS) *
get_transformation(*cs.get(), *pCS.get()) *
BaseVector<length_d>::getQuantityVector().eigenVector_)
.getY();
}
......@@ -55,7 +55,7 @@ namespace corsika {
return BaseVector<length_d>::getQuantityVector().getZ();
} else {
return QuantityVector<length_d>(
get_transformation(cs, pCS) *
get_transformation(*cs.get(), *pCS.get()) *
BaseVector<length_d>::getQuantityVector().eigenVector_)
.getZ();
}
......@@ -68,7 +68,7 @@ namespace corsika {
return BaseVector<length_d>::getQuantityVector();
} else {
return QuantityVector<length_d>(
get_transformation(cs, pCS) *
get_transformation(*cs.get(), *pCS.get()) *
BaseVector<length_d>::getQuantityVector().eigenVector_);
}
}
......@@ -81,7 +81,8 @@ namespace corsika {
void Point::rebase(CoordinateSystemPtr const& pCS) {
BaseVector<length_d>::setQuantityVector(QuantityVector<length_d>(
get_transformation(BaseVector<length_d>::getCoordinateSystem(), pCS) *
get_transformation(*BaseVector<length_d>::getCoordinateSystem().get(),
*pCS.get()) *
BaseVector<length_d>::getQuantityVector().eigenVector_));
BaseVector<length_d>::setCoordinateSystem(pCS);
}
......
......@@ -31,7 +31,8 @@ namespace corsika {
return BaseVector<TDimension>::getQuantityVector();
} else {
return QuantityVector<TDimension>(
get_transformation(BaseVector<TDimension>::getCoordinateSystem(), pCS)
get_transformation(*BaseVector<TDimension>::getCoordinateSystem().get(),
*pCS.get())
.linear() *
BaseVector<TDimension>::getQuantityVector().eigenVector_);
}
......@@ -51,7 +52,8 @@ namespace corsika {
return BaseVector<TDimension>::getQuantityVector()[0];
} else {
return QuantityVector<TDimension>(
get_transformation(BaseVector<TDimension>::getCoordinateSystem(), pCS)
get_transformation(*BaseVector<TDimension>::getCoordinateSystem().get(),
*pCS.get())
.linear() *
BaseVector<TDimension>::getQuantityVector().eigenVector_)[0];
}
......@@ -64,7 +66,8 @@ namespace corsika {
return BaseVector<TDimension>::getQuantityVector()[1];
} else {
return QuantityVector<TDimension>(
get_transformation(BaseVector<TDimension>::getCoordinateSystem(), pCS)
get_transformation(*BaseVector<TDimension>::getCoordinateSystem().get(),
*pCS.get())
.linear() *
BaseVector<TDimension>::getQuantityVector().eigenVector_)[1];
}
......@@ -77,7 +80,8 @@ namespace corsika {
return BaseVector<TDimension>::getQuantityVector()[2];
} else {
return QuantityVector<TDimension>(
get_transformation(BaseVector<TDimension>::getCoordinateSystem(), pCS)
get_transformation(*BaseVector<TDimension>::getCoordinateSystem().get(),
*pCS.get())
.linear() *
BaseVector<TDimension>::getQuantityVector().eigenVector_)[2];
}
......@@ -86,7 +90,9 @@ namespace corsika {
template <typename TDimension>
void Vector<TDimension>::rebase(CoordinateSystemPtr const& pCS) {
BaseVector<TDimension>::setQuantityVector(QuantityVector<TDimension>(
get_transformation(BaseVector<TDimension>::getCoordinateSystem(), pCS).linear() *
get_transformation(*BaseVector<TDimension>::getCoordinateSystem().get(),
*pCS.get())
.linear() *
BaseVector<TDimension>::getQuantityVector().eigenVector_));
BaseVector<TDimension>::setCoordinateSystem(pCS);
}
......
......@@ -22,7 +22,7 @@ n/*
*/
/*
It is essentially a bug of the phys/units package to define the
It is essentially a bug of the phys_units package to define the
operator<< not in the same namespace as the types it is working
on. This breaks ADL (argument-dependent lookup). Here we "fix" this:
*/
......@@ -148,9 +148,6 @@ namespace corsika::units::si {
return conversion_factor_SI_to_HEP<DimFrom>() * q;
}
template <typename T>
bool operator==(DimensionlessType a, T b){ return a.magnitude() == b; }
} // end namespace corsika::units::si
/**
......
......@@ -166,9 +166,11 @@ namespace corsika {
* \f$ \vec{v}^{\text{(to)}} = \mathcal{M} \vec{v}^{\text{(from)}} \f$
* (\f$ \vec{v}^{(.)} \f$ denotes the coordinates/components of the component in
* the indicated CoordinateSystem).
*
* \todo make this a protected member of CoordinateSystem
*/
inline EigenTransform get_transformation(CoordinateSystemPtr const& c1,
CoordinateSystemPtr const& c2);
inline EigenTransform get_transformation(CoordinateSystem const& c1,
CoordinateSystem const& c2);
} // namespace corsika
......
......@@ -53,7 +53,7 @@ namespace corsika {
: eigenVector_{a, b, c} {
static_assert(
std::is_same_v<TDimension, phys::units::dimensionless_d>,
"initialization of dimensionfull QuantityVector with pure numbers not allowed!");
"initialization of dimensionful QuantityVector with pure numbers not allowed!");
}
quantity_type operator[](size_t const index) const;
......
add_subdirectory (common)
add_subdirectory (framework)
add_subdirectory (media)
add_subdirectory (stack)
add_subdirectory (modules)
add_library (CorsikaTesting INTERFACE)
target_include_directories (CorsikaTesting INTERFACE ${CMAKE_SOURCE_DIR}/tests/common/)
/*
* (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
*
* This software is distributed under the terms of the GNU General Public
* Licence version 3 (GPL Version 3). See file LICENSE for a full version of
* the license.
*/
#pragma once
#include <corsika/framework/core/PhysicalUnits.hpp>
#include <catch2/catch.hpp>
namespace corsika::testing {
/**
* comparion of DimensionlessType quantities with the Catch2 Approx
* class.
* This allows code/checks of this type
* `CHECK(v.normalize().norm() == Approx(1),margin(0)) `
*
**/
inline bool operator==(DimensionlessType const a, Catch::Detail::Approx const& b) {
return a.magnitude() == b;
}
}
......@@ -20,6 +20,9 @@ double constexpr absMargin = 1e-6;
CoordinateSystemPtr rootCS = get_root_CoordinateSystem();
/**
* \todo such helper functions should be moved to the FourVector class:
**/
// helper function for energy-momentum
// relativistic energy
auto const energy = [](HEPMassType m, Vector<hepmomentum_d> const& p) {
......@@ -50,14 +53,16 @@ TEST_CASE("rotation") {
COMBoost boost({eProjectileLab, {rootCS, {0_GeV, 0_GeV, 1_GeV}}}, targetMass);
CoordinateSystemPtr rotCS = boost.getRotatedCS();
CHECK(e1.getX(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e1.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e1.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e2.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e2.getY(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
e1.rebase(rotCS);
e2.rebase(rotCS);
e3.rebase(rotCS);
// length of e1, e2 and e3 must all be 1_GeV in rotated CS (not boosted!)
CHECK(e1.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getNorm() / 1_GeV == Approx(1).margin(absMargin));
// z-axis is along z-boost
CHECK(e3.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getZ(rotCS) / 1_GeV == Approx(1).margin(absMargin));
......@@ -67,85 +72,95 @@ TEST_CASE("rotation") {
COMBoost boost({eProjectileLab, {rootCS, {0_GeV, 1_GeV, 1_meV}}}, targetMass);
CoordinateSystemPtr rotCS = boost.getRotatedCS();
CHECK(e1.getX(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e1.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e1.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
e1.rebase(rotCS);
e2.rebase(rotCS);
e3.rebase(rotCS);
// length of e1, e2 and e3 must all be 1_GeV in rotated CS (not boosted!)
CHECK(e1.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getNorm() / 1_GeV == Approx(1).margin(absMargin));
// z-axis is along y-boost
CHECK(e2.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e2.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e2.getZ(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getY(rotCS) / 1_GeV == Approx(-1).margin(absMargin));
CHECK(e3.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
}
SECTION("x-axis in upper half") {
COMBoost boost({eProjectileLab, {rootCS, {1_GeV, 0_GeV, 1_meV}}}, targetMass);
CoordinateSystemPtr rotCS = boost.getRotatedCS();
e1.rebase(rotCS);
e2.rebase(rotCS);
e3.rebase(rotCS);
// length of e1, e2 and e3 must all be 1_GeV in rotated CS (not boosted!)
CHECK(e1.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getNorm() / 1_GeV == Approx(1).margin(absMargin));
// z-axis is along x-boost
CHECK(e1.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e1.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e1.getZ(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e2.getY(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getX(rotCS) / 1_GeV == Approx(-1).margin(absMargin));
CHECK(e3.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
}
SECTION("neg. z-axis") {
COMBoost boost({eProjectileLab, {rootCS, {0_GeV, 0_GeV, -1_GeV}}}, targetMass);
CoordinateSystemPtr rotCS = boost.getRotatedCS();
CHECK(e1.getX(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e1.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e1.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
e1.rebase(rotCS);
e2.rebase(rotCS);
e3.rebase(rotCS);
CHECK(e2.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e2.getY(rotCS) / 1_GeV == Approx(-1).margin(absMargin));
CHECK(e2.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
// length of e1, e2 and e3 must all be 1_GeV in rotated CS (not boosted!)
CHECK(e1.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getZ(rotCS) / 1_GeV == Approx(-1).margin(absMargin));
// z-axis is along -z-boost
CHECK(-e3.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(-e3.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(-e3.getZ(rotCS) / 1_GeV == Approx(1).margin(absMargin));
}
SECTION("x-axis lower half") {
COMBoost boost({eProjectileLab, {rootCS, {1_GeV, 0_GeV, -1_meV}}}, targetMass);
CoordinateSystemPtr rotCS = boost.getRotatedCS();
e1.rebase(rotCS);
e2.rebase(rotCS);
e3.rebase(rotCS);
// length of e1, e2 and e3 must all be 1_GeV in rotated CS (not boosted!)
CHECK(e1.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getNorm() / 1_GeV == Approx(1).margin(absMargin));
// z-axis is along x-boost
CHECK(e1.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e1.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e1.getZ(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e2.getY(rotCS) / 1_GeV == Approx(-1).margin(absMargin));
CHECK(e2.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getX(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
}
SECTION("y-axis lower half") {
COMBoost boost({eProjectileLab, {rootCS, {0_GeV, 1_GeV, -1_meV}}}, targetMass);
CoordinateSystemPtr rotCS = boost.getRotatedCS();
CHECK(e1.getX(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e1.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e1.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
e1.rebase(rotCS);
e2.rebase(rotCS);
e3.rebase(rotCS);
// length of e1, e2 and e3 must all be 1_GeV in rotated CS (not boosted!)
CHECK(e1.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e2.getNorm() / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getNorm() / 1_GeV == Approx(1).margin(absMargin));
// z-axis is along y-boost
CHECK(e2.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e2.getY(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e2.getZ(rotCS) / 1_GeV == Approx(1).margin(absMargin));
CHECK(e3.getX(rotCS) / 1_GeV == Approx(0).margin(absMargin));
CHECK(e3.getY(rotCS) / 1_GeV == Approx(-1).margin(absMargin));
CHECK(e3.getZ(rotCS) / 1_GeV == Approx(0).margin(absMargin));
}
}
......
......@@ -17,7 +17,10 @@
#include <corsika/framework/geometry/Sphere.hpp>
#include <corsika/framework/geometry/Trajectory.hpp>
#include <PhysicalUnitsCatch2.hpp> // namespace corsike::testing
using namespace corsika;
using namespace corsika::testing;
double constexpr absMargin = 1.0e-8;
......@@ -40,7 +43,7 @@ TEST_CASE("transformations between CoordinateSystems") {
CoordinateSystemPtr translatedCS = make_translation(rootCS, translationVector);
CHECK(*translatedCS->getReferenceCS() == *rootCS);
CHECK(translatedCS->getReferenceCS() == rootCS);
CHECK((p1.getCoordinates(translatedCS) + translationVector).getNorm().magnitude() ==
Approx(0).margin(absMargin));
......@@ -65,11 +68,11 @@ TEST_CASE("transformations between CoordinateSystems") {
QuantityVector<length_d> const tv3{0_m, 0_m, 2_m};
CoordinateSystemPtr cs4 = make_translation(cs3, tv3);
CHECK(*cs4->getReferenceCS()->getReferenceCS() == *rootCS);
CHECK(cs4->getReferenceCS()->getReferenceCS() == rootCS);
CHECK(get_transformation(cs3, cs2).isApprox(
CHECK(get_transformation(*cs3.get(), *cs2.get()).isApprox(
make_translation(rootCS, {3_m, -5_m, 0_m})->getTransform()));
CHECK(get_transformation(cs2, cs3).isApprox(
CHECK(get_transformation(*cs2.get(), *cs3.get()).isApprox(
make_translation(rootCS, {-3_m, +5_m, 0_m})->getTransform()));
}
......@@ -78,7 +81,7 @@ TEST_CASE("transformations between CoordinateSystems") {
double const angle = 90. / 180. * M_PI;
CoordinateSystemPtr rotatedCS = make_rotation(rootCS, axis, angle);
CHECK(*rotatedCS->getReferenceCS() == *rootCS);
CHECK(rotatedCS->getReferenceCS() == rootCS);
CHECK(v1.getComponents(rotatedCS)[1].magnitude() ==
Approx((-1. * tesla).magnitude()));
......@@ -237,7 +240,7 @@ TEST_CASE("CoordinateSystem hirarchy") {
CoordinateSystemPtr rootCS = get_root_CoordinateSystem();
CHECK(get_transformation(rootCS, rootCS).isApprox(EigenTransform::Identity()));
CHECK(get_transformation(*rootCS.get(), *rootCS.get()).isApprox(EigenTransform::Identity()));
// define the root coordinate system
CoordinateSystemPtr root = get_root_CoordinateSystem();
......@@ -267,10 +270,10 @@ TEST_CASE("CoordinateSystem hirarchy") {
// all points should be on top of each other
CHECK_FALSE(get_transformation(root, cs2).isApprox(EigenTransform::Identity()));
CHECK(get_transformation(root, cs3).isApprox(EigenTransform::Identity()));
CHECK(get_transformation(root, cs4).isApprox(EigenTransform::Identity()));
CHECK(get_transformation(cs5, cs6).isApprox(EigenTransform::Identity()));
CHECK_FALSE(get_transformation(*root.get(), *cs2.get()).isApprox(EigenTransform::Identity()));
CHECK(get_transformation(*root.get(), *cs3.get()).isApprox(EigenTransform::Identity()));
CHECK(get_transformation(*root.get(), *cs4.get()).isApprox(EigenTransform::Identity()));
CHECK(get_transformation(*cs5.get(), *cs6.get()).isApprox(EigenTransform::Identity()));
CHECK((p1 - p2).getNorm().magnitude() == Approx(0).margin(absMargin));
CHECK((p1 - p3).getNorm().magnitude() == Approx(0).margin(absMargin));
......@@ -330,6 +333,6 @@ TEST_CASE("Trajectories") {
CHECK((base.getNormalizedDirection().getComponents(rootCS) -
QuantityVector<dimensionless_d>{1, 0, 0})
.getNorm() == Approx(0).margin(absMargin));
.getNorm() == Approx(0).margin(absMargin));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment