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

Remove solver_.parameters_ #668

Merged
merged 55 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
263af07
draft of 621
montythind Sep 24, 2024
da7cd22
second draft
montythind Sep 30, 2024
32a16e2
third draft
montythind Oct 1, 2024
f125b16
darft fourth
montythind Oct 1, 2024
c580d79
Merge branch 'main' into allowSolversToModify_621
montythind Oct 1, 2024
03a8105
test fixed
montythind Oct 2, 2024
0904e97
fix cuda test
montythind Oct 2, 2024
f4cf73a
fix cuda tests
montythind Oct 2, 2024
d67add6
fix again
montythind Oct 2, 2024
ecb857a
cuda test fix
montythind Oct 3, 2024
fefab05
removed params
montythind Oct 14, 2024
c481a0b
added tolerance back
montythind Oct 14, 2024
fdb1fb6
fix cuda tests
montythind Oct 16, 2024
1bc0df6
Merge branch 'main' into allowSolversToModify_621
montythind Oct 17, 2024
9f4b7b3
trying to copy tolerances from state
K20shores Oct 30, 2024
4c84691
updating usage of absolute tolerance
K20shores Nov 6, 2024
1b67f79
removed compile errors
K20shores Nov 7, 2024
3e0d09f
moving relative tolerance
K20shores Nov 7, 2024
a5796b1
fixed some backward euler stuff
K20shores Nov 7, 2024
752519e
removing debug print statements
K20shores Nov 8, 2024
601b79e
correctly setting oregonator tolerances
K20shores Nov 8, 2024
aee4f4f
correctly using tolerances
K20shores Nov 8, 2024
f42bfca
updating jit constructor
K20shores Nov 8, 2024
96ddc42
passing the number of species to the constructor
K20shores Nov 8, 2024
96fbfd0
correcting jit tests
K20shores Nov 11, 2024
09c4df2
uncommenting tests
K20shores Nov 11, 2024
f3c71f7
removing comment
K20shores Nov 11, 2024
59460c6
using same calling convention for analytical tests
K20shores Nov 11, 2024
4b81426
simplifying cuda analytical test interface
K20shores Nov 11, 2024
6112c2c
added tests
montythind Nov 13, 2024
bd1829e
move the copy of absolute tolerance into the cuda state constructor
sjsprecious Nov 15, 2024
130b83c
Merge branch 'allowSolversToModify_621' of github.com:NCAR/micm into …
sjsprecious Nov 15, 2024
5a612d6
fix broken CI test
sjsprecious Nov 15, 2024
bacb558
uncomment a GPU test
sjsprecious Nov 18, 2024
009f6ae
handling merge
K20shores Jan 8, 2025
543d57a
removing more template parameters
K20shores Jan 8, 2025
316d005
removing template parameters
K20shores Jan 8, 2025
a450599
removing template parameters
K20shores Jan 8, 2025
d00baca
removing another
K20shores Jan 8, 2025
ae93072
adding typename
K20shores Jan 8, 2025
8f83390
fixing merge conflict in cuda files
K20shores Jan 8, 2025
b63e8c7
trying to get cuda to compile
K20shores Jan 9, 2025
c6e89ab
getting cuda to compile
K20shores Jan 9, 2025
eceb9a8
merging main
K20shores Jan 9, 2025
10b8ab0
merging again
K20shores Jan 9, 2025
7fb1560
removing variable
K20shores Jan 9, 2025
d2770cf
removing getter
K20shores Jan 9, 2025
69fbc05
Merge branch 'allowSolversToModify_621' of https://github.com/NCAR/mi…
K20shores Jan 9, 2025
f34dfca
merging main
K20shores Jan 13, 2025
bc1ea2f
addressing PR comments
K20shores Jan 13, 2025
8aef106
adding a more explicit test
K20shores Jan 13, 2025
bb6d0b5
correcting cuda build
K20shores Jan 13, 2025
63615bf
correcting timing difference between main and this branch
K20shores Jan 13, 2025
37a8870
initializing relative tolerance
K20shores Jan 14, 2025
757e613
removing std::move
K20shores Jan 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions examples/profile_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,15 @@ int Run(const char* filepath, const char* initial_conditions, const std::string&
auto reactions = solver_params.processes_;

auto params = RosenbrockSolverParameters::ThreeStageRosenbrockParameters();
params.relative_tolerance_ = 0.1;


auto solver = VectorBuilder<L>(params)
.SetSystem(chemical_system)
.SetReactions(reactions)
.Build();
auto state = solver.GetState();

state.SetRelativeTolerances(0.1);

state.conditions_[0].temperature_ = dataMap.environments["temperature"]; // K
state.conditions_[0].pressure_ = dataMap.environments["pressure"]; // Pa
state.conditions_[0].air_density_ = dataMap.environments["air_density"]; // mol m-3
Expand Down
15 changes: 12 additions & 3 deletions include/micm/configure/solver_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ namespace micm
System system_;
std::vector<Process> processes_;
RosenbrockSolverParameters parameters_;
double relative_tolerance_;

SolverParameters(const System& system, std::vector<Process>&& processes, const RosenbrockSolverParameters&& parameters)
: system_(system),
Expand All @@ -116,6 +117,13 @@ namespace micm
parameters_(parameters)
{
}
K20shores marked this conversation as resolved.
Show resolved Hide resolved
SolverParameters(System&& system, std::vector<Process>&& processes, RosenbrockSolverParameters&& parameters, double relative_tolerance)
: system_(system),
processes_(processes),
parameters_(parameters),
relative_tolerance_(relative_tolerance)
sjsprecious marked this conversation as resolved.
Show resolved Hide resolved
{
}
};

class ConfigReaderPolicy
Expand All @@ -138,6 +146,7 @@ namespace micm
std::unordered_map<std::string, Phase> phases_;
std::vector<Process> processes_;
RosenbrockSolverParameters parameters_;
double relative_tolerance_;

// Common YAML
inline static const std::string DEFAULT_CONFIG_FILE_JSON = "config.json";
Expand Down Expand Up @@ -273,7 +282,7 @@ namespace micm
processes_.clear();

// Parse species object array
ParseSpeciesArray(species_objects);
ParseSpeciesArray(species_objects);

// Assign the parsed 'Species' to 'Phase'
std::vector<Species> species_arr;
Expand Down Expand Up @@ -426,7 +435,7 @@ namespace micm
void ParseRelativeTolerance(const objectType& object)
{
ValidateSchema(object, { "value", "type" }, {});
this->parameters_.relative_tolerance_ = object["value"].as<double>();
this->relative_tolerance_ = object["value"].as<double>();
}

void ParseMechanism(const objectType& object)
Expand Down Expand Up @@ -959,7 +968,7 @@ namespace micm
SolverParameters GetSolverParams()
{
return SolverParameters(
std::move(System(this->gas_phase_, this->phases_)), std::move(this->processes_), std::move(this->parameters_));
std::move(System(this->gas_phase_, this->phases_)), std::move(this->processes_), std::move(this->parameters_), std::move(this->relative_tolerance_));
K20shores marked this conversation as resolved.
Show resolved Hide resolved
}
};
} // namespace micm
2 changes: 1 addition & 1 deletion include/micm/cuda/solver/cuda_rosenbrock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ namespace micm
/// @param errors The computed errors
/// @return The scaled norm of the errors
template<class DenseMatrixPolicy>
double NormalizedError(const DenseMatrixPolicy& y_old, const DenseMatrixPolicy& y_new, const DenseMatrixPolicy& errors)
double NormalizedError(const DenseMatrixPolicy& y_old, const DenseMatrixPolicy& y_new, const DenseMatrixPolicy& errors, auto& state)
const requires(CudaMatrix<DenseMatrixPolicy>&& VectorizableDense<DenseMatrixPolicy>)
{
return micm::cuda::NormalizedErrorDriver(
Expand Down
4 changes: 2 additions & 2 deletions include/micm/solver/backward_euler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ namespace micm
static bool IsConverged(
const BackwardEulerSolverParameters& parameters,
const DenseMatrixPolicy& residual,
const DenseMatrixPolicy& state) requires(!VectorizableDense<DenseMatrixPolicy>);
const DenseMatrixPolicy& state, auto& stateParams) requires(!VectorizableDense<DenseMatrixPolicy>);
template<class DenseMatrixPolicy>
static bool IsConverged(
const BackwardEulerSolverParameters& parameters,
const DenseMatrixPolicy& residual,
const DenseMatrixPolicy& state) requires(VectorizableDense<DenseMatrixPolicy>);
const DenseMatrixPolicy& state, auto& stateParams) requires(VectorizableDense<DenseMatrixPolicy>);
};

} // namespace micm
Expand Down
15 changes: 7 additions & 8 deletions include/micm/solver/backward_euler.inl
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ namespace micm
continue;

// check for convergence
converged = IsConverged(parameters_, forcing, Yn1);
converged = IsConverged(parameters_, forcing, Yn1, state);
} while (!converged && iterations < max_iter);

if (!converged)
Expand Down Expand Up @@ -180,11 +180,11 @@ namespace micm
inline bool BackwardEuler<RatesPolicy, LinearSolverPolicy>::IsConverged(
const BackwardEulerSolverParameters& parameters,
const DenseMatrixPolicy& residual,
const DenseMatrixPolicy& state) requires(!VectorizableDense<DenseMatrixPolicy>)
const DenseMatrixPolicy& state, auto& stateParams) requires(!VectorizableDense<DenseMatrixPolicy>)
{
double small = parameters.small_;
double rel_tol = parameters.relative_tolerance_;
auto& abs_tol = parameters.absolute_tolerance_;
double rel_tol = stateParams.relative_tolerance_;
auto& abs_tol = stateParams.absolute_tolerance_;
auto residual_iter = residual.AsVector().begin();
auto state_iter = state.AsVector().begin();
const std::size_t n_elem = residual.NumRows() * residual.NumColumns();
Expand All @@ -206,18 +206,17 @@ namespace micm
inline bool BackwardEuler<RatesPolicy, LinearSolverPolicy>::IsConverged(
const BackwardEulerSolverParameters& parameters,
const DenseMatrixPolicy& residual,
const DenseMatrixPolicy& state) requires(VectorizableDense<DenseMatrixPolicy>)
const DenseMatrixPolicy& state, auto& stateParams) requires(VectorizableDense<DenseMatrixPolicy>)
{
double small = parameters.small_;
double rel_tol = parameters.relative_tolerance_;
auto& abs_tol = parameters.absolute_tolerance_;
double rel_tol = stateParams.relative_tolerance_;
auto& abs_tol = stateParams.absolute_tolerance_;
auto residual_iter = residual.AsVector().begin();
auto state_iter = state.AsVector().begin();
const std::size_t n_elem = residual.NumRows() * residual.NumColumns();
const std::size_t L = residual.GroupVectorSize();
const std::size_t n_vars = abs_tol.size();
const std::size_t whole_blocks = std::floor(residual.NumRows() / L) * residual.GroupSize();

// evaluate the rows that fit exactly into the vectorizable dimension (L)
for (std::size_t i = 0; i < whole_blocks; ++i)
{
Expand Down
4 changes: 2 additions & 2 deletions include/micm/solver/rosenbrock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ namespace micm
/// @param errors The computed errors
/// @return
template<class DenseMatrixPolicy>
double NormalizedError(const DenseMatrixPolicy& y, const DenseMatrixPolicy& y_new, const DenseMatrixPolicy& errors) const
double NormalizedError(const DenseMatrixPolicy& y, const DenseMatrixPolicy& y_new, const DenseMatrixPolicy& errors, auto& state) const
requires(!VectorizableDense<DenseMatrixPolicy>);
template<class DenseMatrixPolicy>
double NormalizedError(const DenseMatrixPolicy& y, const DenseMatrixPolicy& y_new, const DenseMatrixPolicy& errors) const
double NormalizedError(const DenseMatrixPolicy& y, const DenseMatrixPolicy& y_new, const DenseMatrixPolicy& errors, auto& state) const
requires(VectorizableDense<DenseMatrixPolicy>);
}; // end of Abstract Rosenbrock Solver

Expand Down
22 changes: 12 additions & 10 deletions include/micm/solver/rosenbrock.inl
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ namespace micm
Yerror.Axpy(parameters_.e_[stage], K[stage]);

// Compute the normalized error
auto error = static_cast<const Derived*>(this)->NormalizedError(Y, Ynew, Yerror);
auto error = static_cast<const Derived*>(this)->NormalizedError(Y, Ynew, Yerror, state);

// New step size is bounded by FacMin <= Hnew/H <= FacMax
double fac = std::min(
Expand Down Expand Up @@ -242,7 +242,8 @@ namespace micm
inline double AbstractRosenbrockSolver<RatesPolicy, LinearSolverPolicy, Derived>::NormalizedError(
const DenseMatrixPolicy& Y,
const DenseMatrixPolicy& Ynew,
const DenseMatrixPolicy& errors) const requires(!VectorizableDense<DenseMatrixPolicy>)
const DenseMatrixPolicy& errors,
auto& state) const requires(!VectorizableDense<DenseMatrixPolicy>)
{
// Solving Ordinary Differential Equations II, page 123
// https://link-springer-com.cuucar.idm.oclc.org/book/10.1007/978-3-642-05221-7
Expand All @@ -253,7 +254,7 @@ namespace micm
auto& _ynew = Ynew.AsVector();
auto& _errors = errors.AsVector();
const std::size_t N = Y.AsVector().size();
const std::size_t n_vars = parameters_.absolute_tolerance_.size();
const std::size_t n_vars = state.absolute_tolerance_.size();

double ymax = 0;
double errors_over_scale = 0;
Expand All @@ -263,7 +264,7 @@ namespace micm
{
ymax = std::max(std::abs(_y[i]), std::abs(_ynew[i]));
errors_over_scale =
_errors[i] / (parameters_.absolute_tolerance_[i % n_vars] + parameters_.relative_tolerance_ * ymax);
_errors[i] / (state.absolute_tolerance_[i % n_vars] + state.relative_tolerance_ * ymax);
error += errors_over_scale * errors_over_scale;
}

Expand All @@ -277,7 +278,8 @@ namespace micm
inline double AbstractRosenbrockSolver<RatesPolicy, LinearSolverPolicy, Derived>::NormalizedError(
const DenseMatrixPolicy& Y,
const DenseMatrixPolicy& Ynew,
const DenseMatrixPolicy& errors) const requires(VectorizableDense<DenseMatrixPolicy>)
const DenseMatrixPolicy& errors,
auto& state) const requires(VectorizableDense<DenseMatrixPolicy>)
{
// Solving Ordinary Differential Equations II, page 123
// https://link-springer-com.cuucar.idm.oclc.org/book/10.1007/978-3-642-05221-7
Expand All @@ -289,7 +291,7 @@ namespace micm
auto errors_iter = errors.AsVector().begin();
const std::size_t N = Y.NumRows() * Y.NumColumns();
const std::size_t L = Y.GroupVectorSize();
const std::size_t n_vars = parameters_.absolute_tolerance_.size();
const std::size_t n_vars = state.absolute_tolerance_.size();

const std::size_t whole_blocks = std::floor(Y.NumRows() / Y.GroupVectorSize()) * Y.GroupSize();

Expand All @@ -300,8 +302,8 @@ namespace micm
for (std::size_t i = 0; i < whole_blocks; ++i)
{
errors_over_scale =
*errors_iter / (parameters_.absolute_tolerance_[(i / L) % n_vars] +
parameters_.relative_tolerance_ * std::max(std::abs(*y_iter), std::abs(*ynew_iter)));
*errors_iter / (state.absolute_tolerance_[(i / L) % n_vars] +
state.relative_tolerance_ * std::max(std::abs(*y_iter), std::abs(*ynew_iter)));
error += errors_over_scale * errors_over_scale;
++y_iter;
++ynew_iter;
Expand All @@ -319,8 +321,8 @@ namespace micm
{
const std::size_t idx = y * L + x;
errors_over_scale = errors_iter[idx] /
(parameters_.absolute_tolerance_[y] +
parameters_.relative_tolerance_ * std::max(std::abs(y_iter[idx]), std::abs(ynew_iter[idx])));
(state.absolute_tolerance_[y] +
state.relative_tolerance_ * std::max(std::abs(y_iter[idx]), std::abs(ynew_iter[idx])));
error += errors_over_scale * errors_over_scale;
}
}
Expand Down
7 changes: 1 addition & 6 deletions include/micm/solver/rosenbrock_solver_parameters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ namespace micm
// Gamma_i = \sum_j gamma_{i,j}
std::array<double, 6> gamma_{};

//We have this to pass CUDA Tests. once we figure out a path forward we can get rid of these.
std::vector<double> absolute_tolerance_;
double relative_tolerance_{ 1e-6 };
montythind marked this conversation as resolved.
Show resolved Hide resolved

Expand Down Expand Up @@ -130,12 +131,6 @@ namespace micm
std::cout << val << " ";
std::cout << std::endl;
std::cout << "absolute_tolerance: ";
for (auto& val : absolute_tolerance_)
{
std::cout << val << " ";
}
std::cout << std::endl;
std::cout << "relative_tolerance: " << relative_tolerance_ << std::endl;
}

inline RosenbrockSolverParameters RosenbrockSolverParameters::TwoStageRosenbrockParameters()
Expand Down
9 changes: 8 additions & 1 deletion include/micm/solver/solver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ namespace micm
return solver_.Solve(time_step, state);
}

// Overloaded Solve function to change parameters
SolverResult Solve(double time_step, StatePolicy& state, SolverParametersType& params)
K20shores marked this conversation as resolved.
Show resolved Hide resolved
{
solver_.parameters_ = params;
return solver_.Solve(time_step, state);
K20shores marked this conversation as resolved.
Show resolved Hide resolved
}

/// @brief Returns the number of grid cells
/// @return
std::size_t GetNumberOfGridCells() const
Expand All @@ -81,7 +88,7 @@ namespace micm
{
return state_parameters_.number_of_rate_constants_;
}

StatePolicy GetState() const
{
auto state = std::move(StatePolicy(state_parameters_));
Expand Down
1 change: 1 addition & 0 deletions include/micm/solver/solver_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ namespace micm
bool reorder_state_ = true;
bool valid_system_ = false;
bool valid_reactions_ = false;
double relative_tolerance_ = 1e-06;
K20shores marked this conversation as resolved.
Show resolved Hide resolved

public:
SolverBuilder() = delete;
Expand Down
9 changes: 6 additions & 3 deletions include/micm/solver/solver_builder.inl
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ namespace micm
}
}
}
}
}

template<
class SolverParametersPolicy,
Expand Down Expand Up @@ -379,8 +379,7 @@ namespace micm
}

this->UnusedSpeciesCheck();
this->SetAbsoluteTolerances(options.absolute_tolerance_, species_map);


RatesPolicy rates(this->reactions_, species_map);
auto nonzero_elements = rates.NonZeroJacobianElements();
auto jacobian = BuildJacobian<SparseMatrixPolicy>(nonzero_elements, this->number_of_grid_cells_, number_of_species);
Expand All @@ -398,6 +397,10 @@ namespace micm
.variable_names_ = variable_names,
.custom_rate_parameter_labels_ = labels,
.nonzero_jacobian_elements_ = nonzero_elements };


this->SetAbsoluteTolerances(state_parameters.absolute_tolerance_, species_map);
K20shores marked this conversation as resolved.
Show resolved Hide resolved
options.absolute_tolerance_ = state_parameters.absolute_tolerance_;

return Solver<SolverPolicy, StatePolicy>(
SolverPolicy(options, std::move(linear_solver), std::move(rates), jacobian),
Expand Down
9 changes: 9 additions & 0 deletions include/micm/solver/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ namespace micm
std::vector<std::string> variable_names_{};
std::vector<std::string> custom_rate_parameter_labels_{};
std::set<std::pair<std::size_t, std::size_t>> nonzero_jacobian_elements_{};
double relative_tolerance_{ 1e-06 };
std::vector<double> absolute_tolerance_ {};
montythind marked this conversation as resolved.
Show resolved Hide resolved
};

template<class DenseMatrixPolicy = StandardDenseMatrix, class SparseMatrixPolicy = StandardSparseMatrix>
Expand Down Expand Up @@ -58,6 +60,8 @@ namespace micm
std::size_t state_size_;
std::size_t number_of_grid_cells_;
std::unique_ptr<TemporaryVariables> temporary_variables_;
double relative_tolerance_;
std::vector<double> absolute_tolerance_;

/// @brief Default constructor
/// Only defined to be used to create default values in types, but a default constructed state is not useable
Expand All @@ -84,6 +88,8 @@ namespace micm
state_size_ = other.state_size_;
number_of_grid_cells_ = other.number_of_grid_cells_;
temporary_variables_ = std::make_unique<TemporaryVariables>(*other.temporary_variables_);
relative_tolerance_ = other.relative_tolerance_;
absolute_tolerance_ = other.absolute_tolerance_;
}

/// @brief Assignment operator
Expand All @@ -106,6 +112,8 @@ namespace micm
state_size_ = other.state_size_;
number_of_grid_cells_ = other.number_of_grid_cells_;
temporary_variables_ = std::make_unique<TemporaryVariables>(*other.temporary_variables_);
relative_tolerance_ = other.relative_tolerance_;
absolute_tolerance_ = other.absolute_tolerance_;
}
return *this;
}
Expand Down Expand Up @@ -179,6 +187,7 @@ namespace micm
/// @param value new parameter value
void SetCustomRateParameter(const std::string& label, double value);
void SetCustomRateParameter(const std::string& label, const std::vector<double>& values);
void SetRelativeTolerances(double relativeTolerance);

/// @brief Print a header of species to display concentrations with respect to time
void PrintHeader();
Expand Down
10 changes: 9 additions & 1 deletion include/micm/solver/state.inl
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ namespace micm
lower_matrix_(),
upper_matrix_(),
state_size_(parameters.variable_names_.size()),
number_of_grid_cells_(parameters.number_of_grid_cells_)
number_of_grid_cells_(parameters.number_of_grid_cells_),
relative_tolerance_(1e-06),
absolute_tolerance_(parameters.absolute_tolerance_)
{
std::size_t index = 0;
for (auto& name : variable_names_)
Expand Down Expand Up @@ -185,6 +187,12 @@ namespace micm
for (std::size_t i = 0; i < custom_rate_parameters_.NumRows(); ++i)
custom_rate_parameters_[i][param->second] = values[i];
}

template<class DenseMatrixPolicy, class SparseMatrixPolicy>
inline void State<DenseMatrixPolicy, SparseMatrixPolicy>::SetRelativeTolerances(double relativeTolerance)
{
this->relative_tolerance_ = relativeTolerance;
}

template<class DenseMatrixPolicy, class SparseMatrixPolicy>
inline void State<DenseMatrixPolicy, SparseMatrixPolicy>::PrintHeader()
Expand Down
2 changes: 0 additions & 2 deletions include/micm/version.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Copyright (C) 2023-2024 National Center for Atmospheric Research
// SPDX-License-Identifier: Apache-2.0
// clang-format off
#pragma once

Expand Down
Loading
Loading