Skip to content
Snippets Groups Projects
Commit 4adbcf24 authored by Anna Wellmann's avatar Anna Wellmann
Browse files

Merge branch 'develop' into feature/rotatingGrids

parents bd0beaf7 ae9bd236
No related branches found
No related tags found
1 merge request!307[GPU] Add a cylinder geometry
Showing with 369 additions and 17 deletions
//=======================================================================================
// ____ ____ __ ______ __________ __ __ __ __
// \ \ | | | | | _ \ |___ ___| | | | | / \ | |
// \ \ | | | | | |_) | | | | | | | / \ | |
// \ \ | | | | | _ / | | | | | | / /\ \ | |
// \ \ | | | | | | \ \ | | | \__/ | / ____ \ | |____
// \ \ | | |__| |__| \__\ |__| \________/ /__/ \__\ |_______|
// \ \ | | ________________________________________________________________
// \ \ | | | ______________________________________________________________|
// \ \| | | | __ __ __ __ ______ _______
// \ | | |_____ | | | | | | | | | _ \ / _____)
// \ | | _____| | | | | | | | | | | \ \ \_______
// \ | | | | |_____ | \_/ | | | | |_/ / _____ |
// \ _____| |__| |________| \_______/ |__| |______/ (_______/
//
// This file is part of VirtualFluids. VirtualFluids is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// VirtualFluids is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with VirtualFluids (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
//
//! \file ActuatorLine.cpp
//! \ingroup ActuatorLine
//! \author Henry Korb, Henrik Asmuth
//=======================================================================================
#define _USE_MATH_DEFINES
#include <cmath>
#include <exception>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
//////////////////////////////////////////////////////////////////////////
#include <basics/DataTypes.h>
#include <basics/PointerDefinitions.h>
#include <basics/StringUtilities/StringUtil.h>
#include <basics/config/ConfigurationFile.h>
#include <logger/Logger.h>
#include <parallel/MPICommunicator.h>
//////////////////////////////////////////////////////////////////////////
#include "GridGenerator/grid/GridBuilder/LevelGridBuilder.h"
#include "GridGenerator/grid/GridBuilder/MultipleGridBuilder.h"
#include "GridGenerator/grid/BoundaryConditions/Side.h"
#include "GridGenerator/grid/BoundaryConditions/BoundaryCondition.h"
#include "GridGenerator/io/SimulationFileWriter/SimulationFileWriter.h"
#include "GridGenerator/io/GridVTKWriter/GridVTKWriter.h"
#include "GridGenerator/TransientBCSetter/TransientBCSetter.h"
//////////////////////////////////////////////////////////////////////////
#include "VirtualFluids_GPU/LBM/Simulation.h"
#include "VirtualFluids_GPU/DataStructureInitializer/GridReaderGenerator/GridGenerator.h"
#include "VirtualFluids_GPU/DataStructureInitializer/GridProvider.h"
#include "VirtualFluids_GPU/DataStructureInitializer/GridReaderFiles/GridReader.h"
#include "VirtualFluids_GPU/Parameter/Parameter.h"
#include "VirtualFluids_GPU/Output/FileWriter.h"
#include "VirtualFluids_GPU/PreCollisionInteractor/ActuatorFarm.h"
#include "VirtualFluids_GPU/PreCollisionInteractor/Probes/PointProbe.h"
#include "VirtualFluids_GPU/PreCollisionInteractor/Probes/PlaneProbe.h"
#include "VirtualFluids_GPU/PreCollisionInteractor/Probes/Probe.h"
#include "VirtualFluids_GPU/Factories/BoundaryConditionFactory.h"
#include "VirtualFluids_GPU/TurbulenceModels/TurbulenceModelFactory.h"
#include "VirtualFluids_GPU/Factories/GridScalingFactory.h"
#include "VirtualFluids_GPU/Kernel/Utilities/KernelTypes.h"
#include "VirtualFluids_GPU/GPU/CudaMemoryManager.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Actuator Line app for regression tests
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::string path(".");
std::string simulationName("ActuatorLine");
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void multipleLevel(const std::string& configPath)
{
vf::parallel::Communicator &communicator = *vf::parallel::MPICommunicator::getInstance();
vf::basics::ConfigurationFile config;
config.load(configPath);
const real reference_diameter = config.getValue<real>("ReferenceDiameter");
const uint nodes_per_diameter = config.getValue<uint>("NodesPerDiameter");
const real velocity = config.getValue<real>("Velocity");
const real L_x = 10 * reference_diameter;
const real L_y = 4 * reference_diameter;
const real L_z = 4 * reference_diameter;
const real viscosity = 1.56e-5;
const real mach = 0.1;
const float tStartOut = config.getValue<real>("tStartOut");
const float tOut = config.getValue<real>("tOut");
const float tEnd = config.getValue<real>("tEnd"); // total time of simulation
const float tStartAveraging = config.getValue<real>("tStartAveraging");
const float tStartTmpAveraging = config.getValue<real>("tStartTmpAveraging");
const float tAveraging = config.getValue<real>("tAveraging");
const float tStartOutProbe = config.getValue<real>("tStartOutProbe");
const float tOutProbe = config.getValue<real>("tOutProbe");
SPtr<Parameter> para = std::make_shared<Parameter>(communicator.getNumberOfProcesses(), communicator.getProcessID(), &config);
BoundaryConditionFactory bcFactory = BoundaryConditionFactory();
GridScalingFactory scalingFactory = GridScalingFactory();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const real dx = reference_diameter/real(nodes_per_diameter);
real turbPos[3] = {3.0f * reference_diameter, 0.0, 0.0};
auto gridBuilder = std::make_shared<MultipleGridBuilder>();
gridBuilder->addCoarseGrid(0.0, -0.5*L_y, -0.5*L_z,
L_x, 0.5*L_y, 0.5*L_z, dx);
gridBuilder->setPeriodicBoundaryCondition(false, false, false);
gridBuilder->buildGrids(false); // buildGrids() has to be called before setting the BCs!!!!
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const real dt = dx * mach / (sqrt(3) * velocity);
const real velocityLB = velocity * dt / dx; // LB units
const real viscosityLB = viscosity * dt / (dx * dx); // LB units
VF_LOG_INFO("dx = {}m", dx);
VF_LOG_INFO("velocity [dx/dt] = {}", velocityLB);
VF_LOG_INFO("viscosity [10^8 dx^2/dt] = {}", viscosityLB*1e8);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
para->setDevices(std::vector<uint>{(uint)0});
para->setOutputPrefix( simulationName );
para->setPrintFiles(true);
para->setVelocityLB(velocityLB);
para->setViscosityLB(viscosityLB);
para->setVelocityRatio( dx / dt );
para->setViscosityRatio( dx*dx/dt );
para->configureMainKernel(vf::collisionKernel::compressible::K17CompressibleNavierStokes);
para->setInitialCondition([&](real coordX, real coordY, real coordZ, real &rho, real &vx, real &vy, real &vz) {
rho = (real)0.0;
vx = velocityLB;
vy = (real)0.0;
vz = (real)0.0;
});
para->setTimestepStartOut( uint(tStartOut/dt) );
para->setTimestepOut( uint(tOut/dt) );
para->setTimestepEnd( uint(tEnd/dt) );
para->setIsBodyForce( true );
para->setUseStreams( true );
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
gridBuilder->setVelocityBoundaryCondition(SideType::MX, velocityLB, 0.0, 0.0);
gridBuilder->setVelocityBoundaryCondition(SideType::MY, velocityLB, 0.0, 0.0);
gridBuilder->setVelocityBoundaryCondition(SideType::PY, velocityLB, 0.0, 0.0);
gridBuilder->setVelocityBoundaryCondition(SideType::MZ, velocityLB, 0.0, 0.0);
gridBuilder->setVelocityBoundaryCondition(SideType::PZ, velocityLB, 0.0, 0.0);
gridBuilder->setPressureBoundaryCondition(SideType::PX, 0.0);
bcFactory.setVelocityBoundaryCondition(BoundaryConditionFactory::VelocityBC::VelocityAndPressureCompressible);
bcFactory.setPressureBoundaryCondition(BoundaryConditionFactory::PressureBC::OutflowNonReflective);
SPtr<TurbulenceModelFactory> tmFactory = std::make_shared<TurbulenceModelFactory>(para);
tmFactory->readConfigFile(config);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int level = 0; // grid level at which the turbine samples velocities and distributes forces
const real smearing_width = dx*exp2(-level)*2; // width of gaussian smearing
VF_LOG_INFO("smearing_width = {}m", smearing_width);
const real density = 1.225f;
const uint nBlades = 3;
const uint nBladeNodes = 32;
const real tipspeed_ratio = 7.5f; // tipspeed ratio = angular vel * radius / inflow vel
const real rotor_speed = 2*tipspeed_ratio*velocity/reference_diameter;
SPtr<ActuatorFarm> actuator_farm = std::make_shared<ActuatorFarm>(nBlades, density, nBladeNodes, smearing_width, level, dt, dx, true);
std::vector<real> bladeRadii;
real dr = reference_diameter/(nBladeNodes*2);
for(uint node=0; node<nBladeNodes; node++){ bladeRadii.emplace_back(dr*(node+1)); }
actuator_farm->addTurbine(turbPos[0], turbPos[1], turbPos[2], reference_diameter, rotor_speed, 0, 0, bladeRadii);
para->addActuator( actuator_farm );
std::vector<real> planePositions = {-1*reference_diameter, 1*reference_diameter, 3*reference_diameter};
for(int i=0; i < planePositions.size(); i++)
{
SPtr<PlaneProbe> planeProbe = std::make_shared<PlaneProbe>("planeProbe_" + std::to_string(i), para->getOutputPath(), tStartTmpAveraging/dt, tAveraging/dt, tStartOutProbe/dt, tOutProbe/dt);
planeProbe->setProbePlane(turbPos[0]+planePositions[i], -0.5 * L_y, -0.5 * L_z, dx, L_y, L_z);
planeProbe->addStatistic(Statistic::Means);
planeProbe->addStatistic(Statistic::Variances);
planeProbe->addStatistic(Statistic::Instantaneous);
para->addProbe( planeProbe );
}
SPtr<PlaneProbe> planeProbeVert = std::make_shared<PlaneProbe>("planeProbeVertical", para->getOutputPath(), tStartTmpAveraging/dt, tAveraging/dt, tStartOutProbe/dt, tOutProbe/dt);
planeProbeVert->setProbePlane(0, turbPos[1], -0.5 * L_z, L_x, dx, L_z);
planeProbeVert->addStatistic(Statistic::Means);
planeProbeVert->addStatistic(Statistic::Variances);
planeProbeVert->addStatistic(Statistic::Instantaneous);
para->addProbe( planeProbeVert );
SPtr<PlaneProbe> planeProbeHorz = std::make_shared<PlaneProbe>("planeProbeHorizontal", para->getOutputPath(), tStartTmpAveraging/dt, tAveraging/dt, tStartOutProbe/dt, tOutProbe/dt);
planeProbeHorz->setProbePlane(0, -0.5 * L_y, turbPos[2], L_x, L_y, dx);
planeProbeHorz->addStatistic(Statistic::Means);
planeProbeHorz->addStatistic(Statistic::Variances);
planeProbeHorz->addStatistic(Statistic::Instantaneous);
para->addProbe( planeProbeHorz );
auto cudaMemoryManager = std::make_shared<CudaMemoryManager>(para);
auto gridGenerator = GridProvider::makeGridGenerator(gridBuilder, para, cudaMemoryManager, communicator);
Simulation sim(para, cudaMemoryManager, communicator, *gridGenerator, &bcFactory, tmFactory, &scalingFactory);
sim.run();
}
int main( int argc, char* argv[])
{
if ( argv != NULL )
{
try
{
vf::logging::Logger::initializeLogger();
if( argc > 1){ path = argv[1]; }
multipleLevel(path + "/apps/gpu/ActuatorLineRegression/configActuatorLine.txt");
}
catch (const spdlog::spdlog_ex &ex) {
std::cout << "Log initialization failed: " << ex.what() << std::endl;
}
catch (const std::bad_alloc& e)
{
VF_LOG_CRITICAL("Bad Alloc: {}", e.what());
}
catch (const std::exception& e)
{
VF_LOG_CRITICAL("exception: {}", e.what());
}
catch (...)
{
VF_LOG_CRITICAL("Unknown exception!");
}
}
return 0;
}
PROJECT(ActuatorLineRegression LANGUAGES CUDA CXX)
vf_add_library(BUILDTYPE binary PRIVATE_LINK basics VirtualFluids_GPU GridGenerator FILES ActuatorLineRegression.cpp)
set_source_files_properties(ActuatorLineRegression.cpp PROPERTIES LANGUAGE CUDA)
set_target_properties(ActuatorLineRegression PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
##################################################
#informations for Writing
##################################################
Path = ./output/ActuatorLine/
##################################################
#informations for reading
##################################################
GridPath=.
##################################################
ReferenceDiameter = 126
NodesPerDiameter = 24
Velocity = 9
##################################################
tStartOut=1000000
tOut=100
tEnd=402
##################################################
tStartTmpAveraging=100
tStartAveraging=100
tAveraging=100
tTmpAveraging=100
tStartOutProbe=100
tOutProbe=100
##################################################
TurbulenceModel = QR
SGSconstant = 0.3333333
QuadricLimiterP = 1.0
QuadricLimiterM = 1.0
QuadricLimiterD = 1.0
##################################################
......@@ -25,5 +25,5 @@ run_regression_test () {
$3
# execute fieldcompare (A more comprehensive manual can be found here https://gitlab.com/dglaeser/fieldcompare)
fieldcompare dir $4 reference_data/$1 --include-files "*.vtu"
fieldcompare dir $4 reference_data/$1 --include-files "*.vtu" $5
}
\ No newline at end of file
#!/bin/bash
source ./regression-tests/__regression_test_executer.sh
# 1. set reference data directory (must match the folder structure in https://github.com/irmb/test_data)
REFERENCE_DATA_DIR=regression_tests/gpu/ActuatorLine
# 2. set cmake flags for the build of VirtualFluids
CMAKE_FLAGS="--preset=make_gpu -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_ARCHITECTURES=75 -DUSER_APPS=apps/gpu/ActuatorLineRegression"
# 3. define the application to be executed
APPLICATION=./build/bin/ActuatorLineRegression
# 4. set the path to the produced data
RESULT_DATA_DIR=output/ActuatorLine
run_regression_test "$REFERENCE_DATA_DIR" "$CMAKE_FLAGS" "$APPLICATION" "$RESULT_DATA_DIR" --ignore-missing-reference-files
......@@ -4,7 +4,7 @@ source ./regression-tests/__regression_test_executer.sh
# 1. set reference data directory (must match the folder structure in https://github.com/irmb/test_data)
REFERENCE_DATA_DIR=regression_tests/cpu/FlowAroundCylinder_2023_09
REFERENCE_DATA_DIR=regression_tests/cpu/FlowAroundCylinder
# 2. set cmake flags for the build of VirtualFluids
CMAKE_FLAGS="--preset=make_cpu -DCMAKE_BUILD_TYPE=Release"
......
......@@ -699,7 +699,7 @@ void Simulation::calculateTimestep(uint timestep)
// File IO
////////////////////////////////////////////////////////////////////////////////
//communicator->startTimer();
if(para->getTimestepOut()>0 && timestep%para->getTimestepOut()==0 && timestep>para->getTimestepStartOut())
if(para->getTimestepOut()>0 && timestep%para->getTimestepOut()==0 && timestep>=para->getTimestepStartOut())
{
//////////////////////////////////////////////////////////////////////////////////
//if (para->getParD(0)->evenOrOdd==true) para->getParD(0)->evenOrOdd=false;
......
......@@ -432,7 +432,7 @@ void Probe::write(Parameter* para, int level, int t)
void Probe::writeParallelFile(Parameter* para, int t)
{
int t_write = this->fileNameLU ? t: t/this->tOut;
std::string filename = this->outputPath + "/" + this->makeParallelFileName(para->getMyProcessID(), t_write);
std::string filename = this->outputPath + this->makeParallelFileName(para->getMyProcessID(), t_write);
std::vector<std::string> nodedatanames = this->getVarNames();
std::vector<std::string> cellNames;
......@@ -444,7 +444,7 @@ void Probe::writeParallelFile(Parameter* para, int t)
void Probe::writeGridFile(Parameter* para, int level, int t, uint part)
{
std::string fname = this->outputPath + "/" + this->makeGridFileName(level, para->getMyProcessID(), t, part);
std::string fname = this->outputPath + this->makeGridFileName(level, para->getMyProcessID(), t, part);
std::vector< UbTupleFloat3 > nodes;
std::vector< std::string > nodedatanames = this->getVarNames();
......@@ -513,10 +513,10 @@ t0 point1.quant1 point2.quant1 ... point1.quant2 point2.quant2 ...
t1 point1.quant1 point2.quant1 ... point1.quant2 point2.quant2 ...
*/
auto probeStruct = this->getProbeStruct(level);
std::string fname = this->outputPath + "/" + this->makeTimeseriesFileName(level, para->getMyProcessID());
std::string fname = this->outputPath + this->makeTimeseriesFileName(level, para->getMyProcessID());
std::ofstream out(fname.c_str(), std::ios::out | std::ios::binary);
if(!out.is_open()) throw std::runtime_error("Could not open timeseries file!");
if(!out.is_open()) throw std::runtime_error("Could not open timeseries file " + fname + "!");
out << "TimeseriesOutput \n";
out << "Quantities: ";
......
......@@ -149,19 +149,19 @@ public:
const bool _hasDeviceQuantityArray,
const bool _outputTimeSeries
): probeName(_probeName),
outputPath(_outputPath),
outputPath(_outputPath + (_outputPath.back() == '/' ? "" : "/")),
tStartAvg(_tStartAvg),
tStartTmpAveraging(_tStartTmpAvg),
tAvg(_tAvg),
tStartOut(_tStartOut),
tOut(_tOut),
hasDeviceQuantityArray(_hasDeviceQuantityArray),
outputTimeSeries(_outputTimeSeries),
outputTimeSeries(_outputTimeSeries),
PreCollisionInteractor()
{
if (tStartOut < tStartAvg) throw std::runtime_error("Probe: tStartOut must be larger than tStartAvg!");
}
void init(Parameter* para, GridProvider* gridProvider, CudaMemoryManager* cudaMemoryManager) override;
void interact(Parameter* para, CudaMemoryManager* cudaMemoryManager, int level, uint t) override;
void free(Parameter* para, CudaMemoryManager* cudaMemoryManager) override;
......
......@@ -22,13 +22,15 @@ namespace vf::lbm
//! - Calculate density and velocity using pyramid summation for low round-off errors as in Eq. (J1)-(J3) \ref
//! <a href="https://doi.org/10.1016/j.camwa.2015.05.001"><b>[ M. Geier et al. (2015), DOI:10.1016/j.camwa 2015.05.001 ]</b></a>
//!
inline __host__ __device__ real getDensity(const real *const &f /*[27]*/)
{
return ((f[dir::DIR_PPP] + f[dir::DIR_MMM]) + (f[dir::DIR_PMP] + f[dir::DIR_MPM])) + ((f[dir::DIR_PMM] + f[dir::DIR_MPP]) + (f[dir::DIR_MMP] + f[dir::DIR_PPM])) +
(((f[dir::DIR_PP0] + f[dir::DIR_MM0]) + (f[dir::DIR_PM0] + f[dir::DIR_MP0])) + ((f[dir::DIR_P0P] + f[dir::DIR_M0M]) + (f[dir::DIR_P0M] + f[dir::DIR_M0P])) +
((f[dir::DIR_0PM] + f[dir::DIR_0MP]) + (f[dir::DIR_0PP] + f[dir::DIR_0MM]))) +
((f[dir::DIR_P00] + f[dir::DIR_M00]) + (f[dir::DIR_0P0] + f[dir::DIR_0M0]) + (f[dir::DIR_00P] + f[dir::DIR_00M])) + f[dir::DIR_000];
return ((((f[dir::DIR_PPP] + f[dir::DIR_MMM]) + (f[dir::DIR_MPM] + f[dir::DIR_PMP])) +
((f[dir::DIR_MPP] + f[dir::DIR_PMM]) + (f[dir::DIR_MMP] + f[dir::DIR_PPM]))) +
(((f[dir::DIR_0MP] + f[dir::DIR_0PM]) + (f[dir::DIR_0MM] + f[dir::DIR_0PP])) +
((f[dir::DIR_M0P] + f[dir::DIR_P0M]) + (f[dir::DIR_M0M] + f[dir::DIR_P0P])) +
((f[dir::DIR_MP0] + f[dir::DIR_PM0]) + (f[dir::DIR_MM0] + f[dir::DIR_PP0]))) +
f[dir::DIR_000]) +
((f[dir::DIR_M00] + f[dir::DIR_P00]) + (f[dir::DIR_0M0] + f[dir::DIR_0P0]) + (f[dir::DIR_00M] + f[dir::DIR_00P]));
}
/*
......@@ -40,14 +42,12 @@ inline __host__ __device__ real getIncompressibleVelocityX1(const real *const &f
(((f[dir::DIR_P0M] - f[dir::DIR_M0P]) + (f[dir::DIR_P0P] - f[dir::DIR_M0M])) + ((f[dir::DIR_PM0] - f[dir::DIR_MP0]) + (f[dir::DIR_PP0] - f[dir::DIR_MM0]))) + (f[dir::DIR_P00] - f[dir::DIR_M00]));
}
inline __host__ __device__ real getIncompressibleVelocityX2(const real *const &f /*[27]*/)
{
return ((((f[dir::DIR_PPP] - f[dir::DIR_MMM]) + (f[dir::DIR_MPM] - f[dir::DIR_PMP])) + ((f[dir::DIR_MPP] - f[dir::DIR_PMM]) + (f[dir::DIR_PPM] - f[dir::DIR_MMP]))) +
(((f[dir::DIR_0PM] - f[dir::DIR_0MP]) + (f[dir::DIR_0PP] - f[dir::DIR_0MM])) + ((f[dir::DIR_MP0] - f[dir::DIR_PM0]) + (f[dir::DIR_PP0] - f[dir::DIR_MM0]))) + (f[dir::DIR_0P0] - f[dir::DIR_0M0]));
}
inline __host__ __device__ real getIncompressibleVelocityX3(const real *const &f /*[27]*/)
{
return ((((f[dir::DIR_PPP] - f[dir::DIR_MMM]) + (f[dir::DIR_PMP] - f[dir::DIR_MPM])) + ((f[dir::DIR_MPP] - f[dir::DIR_PMM]) + (f[dir::DIR_MMP] - f[dir::DIR_PPM]))) +
......
......@@ -20,6 +20,7 @@ stages:
expire_in: 1 hrs
paths:
- output/
when: on_failure
{% for regression_test in regression_tests %}
run-regression-test-{{ regression_test }}:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment