Commit 86e6d1f4 authored by Kirill Terekhov's avatar Kirill Terekhov

Fixes and updates

Silence solvers complaints about parameters.

Updates for Tests/solver_test001

Disable TagVariable and TagVariableArray when USE_AUTODIFF is disabled.

Fixes for Octree example.
parent f97fc84d
......@@ -15,4 +15,5 @@ if(USE_AUTODIFF AND USE_SOLVER AND USE_MESH)
add_subdirectory(ADFVDiscr)
add_subdirectory(ADMFD)
endif(USE_AUTODIFF AND USE_SOLVER AND USE_MESH)
#add_subdirectory(OctreeCutcell)
\ No newline at end of file
#add_subdirectory(OctreeCutcell)
add_subdirectory(Octree)
\ No newline at end of file
CXX=mpicxx
CC=mpicxx
#links for cluster
PETSC_DIR=/data4t/terekhov/Packages/petsc-3.3-p3
PETSC_ARCH=arch-linux2-c-opt
INCPATH = -I../../ #-I/data4t/terekhov/program/include -I${PETSC_DIR}/${PETSC_ARCH}/include -I${PETSC_DIR}/include
OPTFLAGS= -O0 -g -Wall
#OPTFLAGS= -O3
#MYLIBS=../../mspp.a
MYLIBS= ../../msppd.a
CXXFLAGS=$(OPTFLAGS) $(INCPATH)
LDFLAGS=$(OPTFLAGS)
PETSC = -L${PETSC_DIR}/${PETSC_ARCH}/lib/
#-lpetsc -L/usr/X11R6/lib -lX11 -ldmumps -lmumps_common -lmpi_f77 -lscalapack -lpord -lblacs -lparmetis -lmetis -lflapack -lfblas -lmpi -lHYPRE -lsuperlu_dist_2.4
#PETSC+=-lpetsc -L/usr/X11R6/lib64 -lX11 -lpthread -lmpigc4 -lstdc++ -ldmumps -lmumps_common -lscalapack -lpord -lblacs -lparmetis -lmetis -lmpi -lHYPRE -lflapack -lfblas -L/opt/intel/impi/3.2.2.006/lib64 -L/opt/intel/mpi-rt/3.2.2 -lmpigc4 -lmpigf -lmpi ../../ILU2/lib/libilu-2.3.a -lgfortran
PETSC =-lpetsc -L/usr/X11R6/lib -lX11 -lparmetis -lmetis -lmpi_f77 -lflapack -lfblas -lgfortran
ILU2 = ../../ILU2/lib/libilu-2.3.a -lgfortran
LDLIBS=$(MYLIBS) $(PETSC) $(ILU2)
targets=main
all: $(targets)
main: main.o
clean:
rm -f $(targets) *.o
......@@ -27,7 +27,7 @@ if(OPENGL_FOUND)
if(GLUT_FOUND)
include_directories(${OPENGL_INCLUDE_DIR})
include_directories(${GLUT_INCLUDE_DIR})
add_definitions(-D__GRAPHICS__ -g)
add_definitions(-D__GRAPHICS__)
target_link_libraries(Octree ${OPENGL_LIBRARIES} ${GLUT_LIBRARIES})
else(GLUT_FOUND)
message("GLUT not found")
......
This diff is collapsed.
#include "octgrid.h"
#include "oc_glut.h"
#include "my_glut.h"
#include "rotate.h"
#include <math.h>
#include "../../inmost.h"
#include "inmost.h"
#include <iomanip>
#include <iostream>
......@@ -72,7 +72,7 @@ void dump_to_vtk()
else
filename << ".pvtk";
thegrid.mesh->Save(filename.str());
cout << "Process " << rank << ": dumped mesh to file" << endl;
cout << "Process " << ::rank << ": dumped mesh to file" << endl;
}
/// Function provided to octgrid algorithm. Defines transformation from grid to grid for draw.
......@@ -353,7 +353,7 @@ void send_coordinates_to_slaves()
/// special command 'm' to slaves. Slave receive the message 'm', sends his cells to master.
void refresh_slaves_grid()
{
if (rank == 0) { // Send command to slaves for they will sent grid to us
if (::rank == 0) { // Send command to slaves for they will sent grid to us
char buff[10][2];
MPI_Request req[10];
for (int i = 1; i < size; i++)
......@@ -409,7 +409,7 @@ void refresh_slaves_grid()
void prepare_to_correct_brothers()
{
correct_brothers(&thegrid,size,rank, 0);
correct_brothers(&thegrid,size,::rank, 0);
thegrid.mesh->RemoveGhost();
thegrid.mesh->Redistribute();
thegrid.mesh->ReorderEmpty(CELL|FACE|EDGE|NODE);
......@@ -419,7 +419,7 @@ void prepare_to_correct_brothers()
/// Redistribute grid by using partitioner
void redistribute(int type)
{
LOG(2,"Process " << rank << ": redistribute. Cells: " << thegrid.mesh->NumberOfCells())
LOG(2,"Process " << ::rank << ": redistribute. Cells: " << thegrid.mesh->NumberOfCells())
Partitioner * part = new Partitioner(thegrid.mesh);
// Specify the partitioner
......@@ -431,12 +431,12 @@ void redistribute(int type)
part->Evaluate();
delete part;
correct_brothers(&thegrid,size,rank, 2);
correct_brothers(&thegrid,size,::rank, 2);
thegrid.mesh->RemoveGhost();
thegrid.mesh->Redistribute();
thegrid.mesh->ReorderEmpty(CELL|FACE|EDGE|NODE);
thegrid.mesh->AssignGlobalID(CELL | EDGE | FACE | NODE);
LOG(2,"Process " << rank << ": redistribute completed")
LOG(2,"Process " << ::rank << ": redistribute completed")
}
/// Prepare to redistribute. Master sends special command to slaves which means redistribute
......@@ -454,7 +454,7 @@ void redistribute_command()
for (int i = 1; i < size; i++)
{
LOG(3,"Master: send redistribute command to slave " << rank)
LOG(3,"Master: send redistribute command to slave " << ::rank)
buff[i][0] = 'x'; // Special key, means redistribute
buff[i][1] = type + '0'; // Special key, means redistribute
MPI_Isend(buff[i], 2, MPI_CHAR, i, 0, INMOST_MPI_COMM_WORLD, req + i);
......@@ -474,7 +474,7 @@ void keyboard(unsigned char key, int x, int y)
}
if( key == ' ' )
{
if (rank == 0)
if (::rank == 0)
{
send_coordinates_to_slaves();
}
......@@ -485,7 +485,7 @@ void keyboard(unsigned char key, int x, int y)
}
if( key == '[' )
{
if (rank == 0)
if (::rank == 0)
{
send_coordinates_to_slaves();
}
......@@ -500,7 +500,7 @@ void keyboard(unsigned char key, int x, int y)
}
if( key == 'f' )
{
if (rank == 0) { // Send dump command to other process
if (::rank == 0) { // Send dump command to other process
dump_to_vtk();
char buff[10][2];
MPI_Request req[10];
......@@ -565,7 +565,7 @@ void NotMainProcess()
while (1) {
MPI_Recv(buff, 256, MPI_CHAR, 0,0, INMOST_MPI_COMM_WORLD, &status);
LOG(2, "Process " << rank << ": received message '" << buff[0] << "'")
LOG(2, "Process " << ::rank << ": received message '" << buff[0] << "'")
if (buff[0] == 'm') // Need to refine, mouse coordinates come
{
......@@ -637,7 +637,7 @@ void NotMainProcess()
// Теперь мы готовы слать. Пошлем 2мя заходами
// Now we are ready to transmit the data. Transmit with 2 steps
LOG(2, "Process " << rank << ": send buffer with faces = " << count_f << " of " << count_df << ". Edges " << count_e)
LOG(2, "Process " << ::rank << ": send buffer with faces = " << count_f << " of " << count_df << ". Edges " << count_e)
MPI_Send(buff_f, offset ,MPI_CHAR,0,0,INMOST_MPI_COMM_WORLD);
MPI_Send(buff_e,count_e*sizeof(double) + sizeof(int),MPI_CHAR,0,0,INMOST_MPI_COMM_WORLD);
}
......@@ -654,18 +654,18 @@ int main(int argc, char ** argv)
thegrid.cell_should_split = cell_should_split;
thegrid.cell_should_unite = cell_should_unite;
Mesh::Initialize(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_rank(MPI_COMM_WORLD, &::rank);
gridInit(&thegrid,n);
Partitioner::Initialize(&argc,&argv);
size = thegrid.mesh->GetProcessorsNumber();
rank = thegrid.mesh->GetProcessorRank();
::size = thegrid.mesh->GetProcessorsNumber();
::rank = thegrid.mesh->GetProcessorRank();
//dump_to_vtk();
if (rank != 0)
if (::rank != 0)
{
NotMainProcess();
}
......
......@@ -376,6 +376,7 @@ Cell get_adjacent_cell(struct grid* g, Node node, int cell_num, int* result)
}
}
*result = -1;
return InvalidCell();
}
/// Returns true if edge is not a greatest in this cell
......
#ifndef _OCTGRID_H
#define _OCTGRID_H
#include "../../inmost.h"
#include "inmost.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
......
......@@ -15,7 +15,7 @@
Specific: glut.h
**********************************/
#include "oc_glut.h"
#include "my_glut.h"
#include "rotate.h"
#include "math.h"
#include <stdio.h>
......
......@@ -658,16 +658,6 @@ namespace INMOST
Storage::reference & operator [](const Storage & arg) const {return arg.Reference(*static_cast<const Tag*>(this));}
};
class TagVariable : public Tag
{
public:
TagVariable() : Tag() {}
TagVariable(const TagVariable & b) : Tag(b) {}
TagVariable(const Tag & b) : Tag(b) {}
TagVariable & operator = (TagVariable const & b) {Tag::operator =(b); return *this;}
TagVariable & operator = (Tag const & b) {Tag::operator =(b); return *this;}
Storage::var & operator [](const Storage & arg) const {return arg.Variable(*static_cast<const Tag*>(this));}
};
class TagRealArray : public Tag
{
......@@ -713,6 +703,18 @@ namespace INMOST
Storage::reference_array operator [](const Storage & arg) const {return arg.ReferenceArray(*static_cast<const Tag*>(this));}
};
#if defined(USE_AUTODIFF)
class TagVariable : public Tag
{
public:
TagVariable() : Tag() {}
TagVariable(const TagVariable & b) : Tag(b) {}
TagVariable(const Tag & b) : Tag(b) {}
TagVariable & operator = (TagVariable const & b) {Tag::operator =(b); return *this;}
TagVariable & operator = (Tag const & b) {Tag::operator =(b); return *this;}
Storage::var & operator [](const Storage & arg) const {return arg.Variable(*static_cast<const Tag*>(this));}
};
class TagVariableArray : public Tag
{
public:
......@@ -723,6 +725,7 @@ namespace INMOST
TagVariableArray & operator = (Tag const & b) {Tag::operator =(b); return *this;}
Storage::var_array operator [](const Storage & arg) const {return arg.VariableArray(*static_cast<const Tag*>(this));}
};
#endif //USE_AUTODIFF
//////////////////////////////////////////////////////////////////////
/// Inline functions for class Tag //
......
......@@ -157,6 +157,7 @@ namespace INMOST
case Element::Prism:
{
ElementArray<Node> nodes = it->getNodes();
if( nodes.size() != VtkElementNodes(it->GetGeometricType()) ) goto safe_output;
values.push_back(static_cast<integer>(nodes.size()));
for(ElementArray<Node>::iterator jt = nodes.begin(); jt != nodes.end(); jt++)
values.push_back(jt->IntegerDF(set_id));
......@@ -212,11 +213,10 @@ namespace INMOST
safe_output:
//printf("polyhedron!!!\n");
ElementArray<Face> faces = it->getFaces();
integer totalNum = 1 + static_cast<integer>(faces.size());
for(ElementArray<Face>::iterator jt = faces.begin(); jt != faces.end(); jt++)
totalNum += jt->nbAdjElements(NODE);
values.push_back(totalNum);
integer totalNum = 1 + static_cast<integer>(faces.size());
for(ElementArray<Face>::iterator jt = faces.begin(); jt != faces.end(); jt++)
totalNum += jt->nbAdjElements(NODE);
values.push_back(totalNum);
values.push_back(static_cast<integer>(faces.size()));
for(ElementArray<Face>::iterator jt = faces.begin(); jt != faces.end(); jt++)
{
......
......@@ -5,6 +5,8 @@
#include <sstream>
#include "inmost_sparse.h"
#define SILENCE_SET_PARAMETER
namespace INMOST {
#if defined(USE_SOLVER)
......
......@@ -190,7 +190,7 @@ namespace INMOST {
input.open(xml_database);
if (input.fail()) {
std::cout << __FILE__ << ": XML database file not found " << std::endl;
std::cout << __FILE__ << ":" << __LINE__ << ": XML database file " << xml_database << " not found." << std::endl;
return;
}
......@@ -200,7 +200,7 @@ namespace INMOST {
XMLReader::XMLTree root = reader.ReadXML();
if (root.tag.name != "SolverParameters") {
std::cout << __FILE__ << ": Bad XML database file" << std::endl;
std::cout << __FILE__ << ":" << __LINE__ << ": Bad XML database file " << xml_database << "!" << std::endl;
return;
}
......
......@@ -19,29 +19,27 @@ namespace INMOST {
throw INMOST::SolverUnsupportedOperation;
}
void SolverInner::Setup(int *argc, char ***argv, SolverParameters &p) {
if (!p.internalFile.empty()) {
void SolverInner::Setup(int *argc, char ***argv, SolverParameters &p)
{
char line[4096];
char parameterName[4096];
char parameterValue[4096];
if (!p.internalFile.empty())
{
FILE *databaseFile = fopen(p.internalFile.c_str(), "r");
if (!databaseFile) {
return;
}
char *tmp = (char *) calloc(256, sizeof(char));
char *parameterName = (char *) calloc(128, sizeof(char));
char *parameterValue = (char *) calloc(128, sizeof(char));
while (!feof(databaseFile) && fgets(tmp, 256, databaseFile)) {
char *line = tmp;
//Comment str
if (!databaseFile) return;
while (!feof(databaseFile) && fgets(line, 4096, databaseFile))
{
if (line[0] == '#') continue;
sscanf(line, "%s %s", parameterName, parameterValue);
this->SetParameter(parameterName, parameterValue);
}
free(parameterValue);
free(parameterName);
free(tmp);
} else {
for (parameters_iterator_t parameter = p.parameters.begin(); parameter < p.parameters.end(); parameter++) {
}
else
{
for (parameters_iterator_t parameter = p.parameters.begin(); parameter < p.parameters.end(); parameter++)
this->SetParameter((*parameter).first, (*parameter).second);
}
}
}
......@@ -78,7 +76,9 @@ namespace INMOST {
else if (name == "relative_tolerance") return to_string(rtol);
else if (name == "divergence_tolerance") return to_string(dtol);
else {
#if !defined(SILENCE_SET_PARAMETER)
std::cout << "Parameter " << name << " is unknown" << std::endl;
#endif
return "";
}
}
......@@ -89,7 +89,9 @@ namespace INMOST {
else if (name == "absolute_tolerance") atol = atof(val);
else if (name == "relative_tolerance") rtol = atof(val);
else if (name == "divergence_tolerance") dtol = atof(val);
#if !defined(SILENCE_SET_PARAMETER)
else std::cout << "Parameter " << name << " is unknown" << std::endl;
#endif
}
const INMOST_DATA_ENUM_TYPE SolverInner::Iterations() const {
......
......@@ -5,7 +5,7 @@
#include "Source/Misc/utils.h"
#include "solver_prototypes.hpp"
#include "solver_bcgsl.hpp"
#define KSOLVER BCGS_solver
namespace INMOST {
......
......@@ -7,7 +7,6 @@
#include "inmost_solver.h"
#define KSOLVER BCGSL_solver
using namespace INMOST;
......
......@@ -233,7 +233,9 @@ namespace INMOST {
else if (name == "drop_tolerance") return to_string(drop_tolerance);
else if (name == "fill_level") return to_string(fill_level);
else {
#if !defined(SILENCE_SET_PARAMETER)
std::cout << "Parameter " << name << " is unknown (Use internal file for all parameters)" << std::endl;
#endif
return "";
}
}
......@@ -247,7 +249,9 @@ namespace INMOST {
else if (name == "divergence_tolerance") dtol = atof(val);
else if (name == "drop_tolerance") drop_tolerance = atof(val);
else if (name == "fill_level") fill_level = atof(val);
#if !defined(SILENCE_SET_PARAMETER)
else std::cout << "Parameter " << name << " is unknown (Use internal file for all parameters)" << std::endl;
#endif
}
const INMOST_DATA_ENUM_TYPE SolverPETSc::Iterations() const {
......
......@@ -114,13 +114,17 @@ namespace INMOST {
}
std::string SolverSUPERLU::GetParameter(std::string name) const {
#if !defined(SILENCE_SET_PARAMETER)
std::cout << "SolverSUPERLU::GetParameter unsupported operation" << std::endl;
#endif
//throw INMOST::SolverUnsupportedOperation;
return "";
}
void SolverSUPERLU::SetParameter(std::string name, std::string value) {
#if !defined(SILENCE_SET_PARAMETER)
std::cout << "SolverSUPERLU::SetParameter unsupported operation" << std::endl;
#endif
//throw INMOST::SolverUnsupportedOperation;
}
......
......@@ -124,7 +124,9 @@ namespace INMOST {
else if( name == "drop_tolerance") return to_string(drop_tolerance);
else if( name == "fill_level") return to_string(fill_level);
else {
#if !defined(SILENCE_SET_PARAMETER)
std::cout << "Parameter " << name << " is unknown (Use internal file for all parameters)" << std::endl;
#endif
return "";
}
}
......@@ -136,7 +138,9 @@ namespace INMOST {
else if( name == "relative_tolerance") rtol = atof(val);
else if( name == "drop_tolerance") drop_tolerance = atof(val);
else if( name == "fill_level") fill_level = atoi(val);
#if !defined(SILENCE_SET_PARAMETER)
else std::cout << "Parameter " << name << " is unknown (Use internal file for all parameters)" << std::endl;
#endif
}
const INMOST_DATA_ENUM_TYPE SolverTrilinos::Iterations() const {
......
......@@ -39,6 +39,12 @@ if(USE_SOLVER_SUPERLU)
target_link_libraries(solver_test001 ${SUPERLU_LIBRARIES})
endif()
set(SOLVERS inner_ilu2
inner_mptilu2
inner_mptiluc)
foreach(solver ${SOLVERS})
set(TOKAMAK_TESTS utm300
utm1700a
utm1700b
......@@ -46,7 +52,7 @@ set(TOKAMAK_TESTS utm300
utm5940)
foreach(test ${TOKAMAK_TESTS})
add_test(NAME solver_test001_sparsekit_tokamak_${test} COMMAND $<TARGET_FILE:solver_test001> ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/tokamak/${test}.mtx ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/tokamak/${test}_rhs1.mtx)
add_test(NAME solver_test001_sparsekit_tokamak_${solver}_${test} COMMAND $<TARGET_FILE:solver_test001> ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/tokamak/${test}.mtx ${solver} ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/tokamak/${test}_rhs1.mtx)
endforeach()
......@@ -78,7 +84,7 @@ set(DRIVCAV_OLD_TESTS cavity01
cavity26)
foreach(test ${DRIVCAV_OLD_TESTS})
add_test(NAME solver_test001_sparsekit_drivcav_old_${test} COMMAND $<TARGET_FILE:solver_test001> ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/drivcav_old/${test}.mtx ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/drivcav_old/${test}_rhs1.mtx)
add_test(NAME solver_test001_sparsekit_drivcav_old_${solver}_${test} COMMAND $<TARGET_FILE:solver_test001> ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/drivcav_old/${test}.mtx ${solver} ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/drivcav_old/${test}_rhs1.mtx)
endforeach()
......@@ -116,7 +122,7 @@ set(DRIVCAV_TESTS e05r0000
foreach(test ${DRIVCAV_TESTS})
add_test(NAME solver_test001_sparsekit_drivcav_${test} COMMAND $<TARGET_FILE:solver_test001> ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/drivcav/${test}.mtx ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/drivcav/${test}_rhs1.mtx)
add_test(NAME solver_test001_sparsekit_drivcav_${solver}_${test} COMMAND $<TARGET_FILE:solver_test001> ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/drivcav/${test}.mtx ${solver} ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/drivcav/${test}_rhs1.mtx)
endforeach()
......@@ -170,6 +176,7 @@ set(FIDAP_TESTS fidap001
foreach(test ${FIDAP_TESTS})
add_test(NAME solver_test001_sparsekit_fidap_${test} COMMAND $<TARGET_FILE:solver_test001> ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/fidap/${test}.mtx ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/fidap/${test}_rhs1.mtx)
add_test(NAME solver_test001_sparsekit_fidap_${solver}_${test} COMMAND $<TARGET_FILE:solver_test001> ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/fidap/${test}.mtx ${solver} ${CMAKE_CURRENT_SOURCE_DIR}/matrices/sparskit/fidap/${test}_rhs1.mtx)
endforeach()
endforeach(solver ${SOLVERS})
\ No newline at end of file
#include <string>
#include <iostream>
#include "../../inmost.h"
#include "inmost.h"
using namespace INMOST;
#if defined(USE_MPI)
......@@ -15,10 +15,11 @@ int main(int argc, char ** argv)
int rank,procs;
if( argc < 2 )
{
std::cout << "Usage: " << argv[0] << " matrix.mtx [right_hand_side.rhs]" << std::endl;
std::cout << "Usage: " << argv[0] << " matrix.mtx [solver_type] [right_hand_side.rhs]" << std::endl;
std::cout << "solver_type: inner_ilu2 (default), inner_mptilu2, inner_mptiluc, inner_ddpqiluc." << std::endl;
return -1;
}
Solver::Type type = Solver::INNER_MPTILU2;
Solver::Type type = Solver::INNER_ILU2;
Solver::Initialize(&argc,&argv,NULL); // Initialize the linear solver in accordance with args
{
#if defined(USE_MPI)
......@@ -28,6 +29,22 @@ int main(int argc, char ** argv)
rank = 0;
procs = 1;
#endif
if( argc > 2 )
{
std::string stype(argv[2]);
for(size_t k = 0; k < stype.size(); ++k) stype[k] = tolower(stype[k]);
if( stype == "inner_mptilu2" )
type = Solver::INNER_MPTILU2;
else if( stype == "inner_mptiluc" )
type = Solver::INNER_MPTILUC;
else if( stype == "inner_ddpqiluc" )
type = Solver::INNER_DDPQILUC;
else if( stype != "inner_ilu2" && stype != "*" )
{
std::cout << "Unknown type of the solver " << stype << std::endl;
return -1;
}
}
//std::cout << rank << "/" << procs << " " << argv[0] << std::endl;
Sparse::Matrix mat("A"); // Declare the matrix of the linear system to be solved
Sparse::Vector b("rhs"); // Declare the right-hand side vector
......@@ -39,10 +56,10 @@ int main(int argc, char ** argv)
if( !rank ) std::cout << "load matrix: " << Timer() - t << std::endl;
//mat.Save("test.mtx");
t = Timer();
if( argc > 2 )
if( argc > 3 )
{
//std::cout << rank << " load vector from " << std::string(argv[3]) << std::endl;
b.Load(std::string(argv[2])); // Load RHS vector
b.Load(std::string(argv[3])); // Load RHS vector
}
else // Set local RHS to 1 if it was not specified
{
......
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