Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
solverstack
ScalFMM
Commits
c6d35276
Commit
c6d35276
authored
Mar 05, 2018
by
COULAUD Olivier
Browse files
Remove some problems detected by cppcheck
parent
b07e0909
Changes
6
Hide whitespace changes
Inline
Side-by-side
Src/Containers/FBlockAllocator.hpp
View file @
c6d35276
// See LICENCE file at project root
#ifndef FBLOCKALLOCATOR_HPP
#define FBLOCKALLOCATOR_HPP
...
...
@@ -13,16 +14,16 @@
template
<
class
ObjectClass
>
class
FAbstractBlockAllocator
{
public:
FAbstractBlockAllocator
(){}
FAbstractBlockAllocator
(){}
virtual
~
FAbstractBlockAllocator
(){
}
virtual
~
FAbstractBlockAllocator
(){
}
FAbstractBlockAllocator
(
const
FAbstractBlockAllocator
&
)
=
delete
;
FAbstractBlockAllocator
&
operator
=
(
const
FAbstractBlockAllocator
&
)
=
delete
;
FAbstractBlockAllocator
(
const
FAbstractBlockAllocator
&
)
=
delete
;
FAbstractBlockAllocator
&
operator
=
(
const
FAbstractBlockAllocator
&
)
=
delete
;
virtual
ObjectClass
*
newObject
()
=
0
;
virtual
void
deleteObject
(
const
ObjectClass
*
)
=
0
;
virtual
ObjectClass
*
newObject
()
=
0
;
virtual
void
deleteObject
(
const
ObjectClass
*
)
=
0
;
};
/**
...
...
@@ -31,13 +32,13 @@ public:
template
<
class
ObjectClass
>
class
FBasicBlockAllocator
:
public
FAbstractBlockAllocator
<
ObjectClass
>
{
public:
ObjectClass
*
newObject
(){
return
new
ObjectClass
;
}
ObjectClass
*
newObject
(){
return
new
ObjectClass
;
}
void
deleteObject
(
const
ObjectClass
*
inObject
){
delete
inObject
;
}
void
deleteObject
(
const
ObjectClass
*
inObject
){
delete
inObject
;
}
};
...
...
@@ -46,98 +47,98 @@ public:
*/
template
<
class
ObjectClass
,
int
SizeOfBlock
>
class
FListBlockAllocator
:
public
FAbstractBlockAllocator
<
ObjectClass
>
{
static_assert
(
SizeOfBlock
>=
1
,
"SizeOfBlock should be 1 minimum"
);
class
CellsBlock
{
CellsBlock
(
const
CellsBlock
&
)
=
delete
;
CellsBlock
&
operator
=
(
const
CellsBlock
&
)
=
delete
;
int
nbObjectInBlock
;
ObjectClass
*
const
objectPtr
;
unsigned
char
usedBlocks
[
SizeOfBlock
];
unsigned
char
objectMemory
[
SizeOfBlock
*
sizeof
(
ObjectClass
)];
public:
CellsBlock
()
:
nbObjectInBlock
(
0
),
objectPtr
(
reinterpret_cast
<
ObjectClass
*>
(
objectMemory
)){
memset
(
usedBlocks
,
0
,
sizeof
(
unsigned
char
)
*
SizeOfBlock
);
}
~
CellsBlock
(){
FAssertLF
(
nbObjectInBlock
==
0x0
);
}
bool
isInsideBlock
(
const
ObjectClass
*
const
cellPtr
)
const
{
return
objectPtr
<=
cellPtr
&&
cellPtr
<
&
objectPtr
[
SizeOfBlock
];
}
int
getPositionInBlock
(
const
ObjectClass
*
const
cellPtr
)
const
{
const
int
position
=
int
((
cellPtr
-
objectPtr
));
FAssertLF
(
usedBlocks
[
position
]
!=
0x0
);
return
position
;
}
void
deleteObject
(
const
int
position
){
FAssertLF
(
usedBlocks
[
position
]
!=
0x0
);
objectPtr
[
position
].
~
ObjectClass
();
nbObjectInBlock
-=
1
;
usedBlocks
[
position
]
=
0x0
;
}
ObjectClass
*
getNewObject
(){
for
(
int
idx
=
0
;
idx
<
SizeOfBlock
;
++
idx
){
if
(
usedBlocks
[
idx
]
==
0
){
nbObjectInBlock
+=
1
;
usedBlocks
[
idx
]
=
0x1
;
new
(
&
(
objectPtr
[
idx
]))
ObjectClass
;
return
&
(
objectPtr
[
idx
]);
}
}
return
nullptr
;
}
int
isEmpty
()
const
{
return
nbObjectInBlock
==
0
;
}
int
isFull
()
const
{
return
nbObjectInBlock
==
SizeOfBlock
;
}
};
std
::
list
<
CellsBlock
>
blocks
;
static_assert
(
SizeOfBlock
>=
1
,
"SizeOfBlock should be 1 minimum"
);
class
CellsBlock
{
CellsBlock
(
const
CellsBlock
&
)
=
delete
;
CellsBlock
&
operator
=
(
const
CellsBlock
&
)
=
delete
;
int
nbObjectInBlock
;
ObjectClass
*
const
objectPtr
;
unsigned
char
usedBlocks
[
SizeOfBlock
];
unsigned
char
objectMemory
[
SizeOfBlock
*
sizeof
(
ObjectClass
)];
public:
CellsBlock
()
:
nbObjectInBlock
(
0
),
objectPtr
(
reinterpret_cast
<
ObjectClass
*>
(
objectMemory
)){
memset
(
usedBlocks
,
0
,
sizeof
(
unsigned
char
)
*
SizeOfBlock
);
}
~
CellsBlock
(){
FAssertLF
(
nbObjectInBlock
==
0x0
);
}
bool
isInsideBlock
(
const
ObjectClass
*
const
cellPtr
)
const
{
return
objectPtr
<=
cellPtr
&&
cellPtr
<
&
objectPtr
[
SizeOfBlock
];
}
int
getPositionInBlock
(
const
ObjectClass
*
const
cellPtr
)
const
{
const
int
position
=
int
((
cellPtr
-
objectPtr
));
FAssertLF
(
usedBlocks
[
position
]
!=
0x0
);
return
position
;
}
void
deleteObject
(
const
int
position
){
FAssertLF
(
usedBlocks
[
position
]
!=
0x0
);
objectPtr
[
position
].
~
ObjectClass
();
nbObjectInBlock
-=
1
;
usedBlocks
[
position
]
=
0x0
;
}
ObjectClass
*
getNewObject
(){
for
(
int
idx
=
0
;
idx
<
SizeOfBlock
;
++
idx
){
if
(
usedBlocks
[
idx
]
==
0
){
nbObjectInBlock
+=
1
;
usedBlocks
[
idx
]
=
0x1
;
new
(
&
(
objectPtr
[
idx
]))
ObjectClass
;
return
&
(
objectPtr
[
idx
]);
}
}
return
nullptr
;
}
int
isEmpty
()
const
{
return
nbObjectInBlock
==
0
;
}
int
isFull
()
const
{
return
nbObjectInBlock
==
SizeOfBlock
;
}
};
std
::
list
<
CellsBlock
>
blocks
;
public:
ObjectClass
*
newObject
(){
typename
std
::
list
<
CellsBlock
>::
iterator
iterator
(
blocks
.
begin
());
const
typename
std
::
list
<
CellsBlock
>::
iterator
end
(
blocks
.
end
());
while
(
iterator
!=
end
){
if
(
!
(
*
iterator
).
isFull
()
){
return
(
*
iterator
).
getNewObject
();
}
++
iterator
;
}
blocks
.
emplace_back
();
return
blocks
.
back
().
getNewObject
();
ObjectClass
*
newObject
(){
typename
std
::
list
<
CellsBlock
>::
iterator
iterator
(
blocks
.
begin
());
const
typename
std
::
list
<
CellsBlock
>::
iterator
end
(
blocks
.
end
());
while
(
iterator
!=
end
){
if
(
!
(
*
iterator
).
isFull
()
){
return
(
*
iterator
).
getNewObject
();
}
++
iterator
;
}
void
deleteObject
(
const
ObjectClass
*
inObject
){
typename
std
::
list
<
CellsBlock
>::
iterator
iterator
(
blocks
.
begin
());
const
typename
std
::
list
<
CellsBlock
>::
iterator
end
(
blocks
.
end
());
while
(
iterator
!=
end
){
if
(
(
*
iterator
).
isInsideBlock
(
inObject
)
){
(
*
iterator
).
deleteObject
((
*
iterator
).
getPositionInBlock
(
inObject
));
if
(
(
*
iterator
).
isEmpty
()
){
blocks
.
erase
(
iterator
);
}
break
;
}
++
iterator
;
}
blocks
.
emplace_back
();
return
blocks
.
back
().
getNewObject
();
}
void
deleteObject
(
const
ObjectClass
*
inObject
){
typename
std
::
list
<
CellsBlock
>::
iterator
iterator
(
blocks
.
begin
());
const
typename
std
::
list
<
CellsBlock
>::
iterator
end
(
blocks
.
end
());
while
(
iterator
!=
end
){
if
(
(
*
iterator
).
isInsideBlock
(
inObject
)
){
(
*
iterator
).
deleteObject
((
*
iterator
).
getPositionInBlock
(
inObject
));
if
(
(
*
iterator
).
isEmpty
()
){
blocks
.
erase
(
iterator
);
}
break
;
}
++
iterator
;
}
}
};
#endif // FBLOCKALLOCATOR_HPP
Src/Kernels/Chebyshev/FChebM2LHandler.hpp
View file @
c6d35276
...
...
@@ -308,10 +308,11 @@ FChebM2LHandler<FReal, ORDER, MatrixKernelClass>::ComputeAndCompress(const Matri
FBlas
::
setzero
(
rank
*
rank
,
C
+
idx
*
rank
*
rank
);
}
}
delete
[]
_C
;
if
(
counter
!=
ninteractions
){
throw
std
::
runtime_error
(
"Number of interactions must correspond to 316"
);
}
delete
[]
_C
;
//////////////////////////////////////////////////////////
...
...
Src/Kernels/Chebyshev/FChebSymM2LHandler.hpp
View file @
c6d35276
...
...
@@ -52,14 +52,14 @@ static void precompute(const MatrixKernelClass *const MatrixKernel, const FReal
// set roots of target cell (X)
FChebTensor
<
FReal
,
ORDER
>::
setRoots
(
FPoint
<
FReal
>
(
0.
,
0.
,
0.
),
CellWidth
,
X
);
// temporary matrix
FReal
*
U
=
new
FReal
[
nnodes
*
nnodes
];
FReal
*
U
=
new
FReal
[
nnodes
*
nnodes
]
{}
;
// needed for the SVD
int
INFO
;
const
unsigned
int
LWORK
=
2
*
(
3
*
nnodes
+
nnodes
);
FReal
*
const
WORK
=
new
FReal
[
LWORK
];
FReal
*
const
VT
=
new
FReal
[
nnodes
*
nnodes
];
FReal
*
const
S
=
new
FReal
[
nnodes
];
FReal
*
const
WORK
=
new
FReal
[
LWORK
]
{}
;
FReal
*
const
VT
=
new
FReal
[
nnodes
*
nnodes
]
{}
;
FReal
*
const
S
=
new
FReal
[
nnodes
]
{}
;
// initialize timer
...
...
@@ -122,17 +122,17 @@ static void precompute(const MatrixKernelClass *const MatrixKernel, const FReal
#endif
// QR decomposition
FReal
*
phi
=
new
FReal
[
rank
*
rank
];
FReal
*
phi
=
new
FReal
[
rank
*
rank
]
{}
;
{
// QR of U and V
FReal
*
tauU
=
new
FReal
[
rank
];
FReal
*
tauU
=
new
FReal
[
rank
]
{}
;
INFO
=
FBlas
::
geqrf
(
nnodes
,
rank
,
UU
,
tauU
,
LWORK
,
WORK
);
assert
(
INFO
==
0
);
FReal
*
tauV
=
new
FReal
[
rank
];
FReal
*
tauV
=
new
FReal
[
rank
]
{}
;
INFO
=
FBlas
::
geqrf
(
nnodes
,
rank
,
VV
,
tauV
,
LWORK
,
WORK
);
assert
(
INFO
==
0
);
// phi = Ru Rv'
FReal
*
rU
=
new
FReal
[
2
*
rank
*
rank
];
FReal
*
rU
=
new
FReal
[
2
*
rank
*
rank
]
{}
;
FReal
*
rV
=
rU
+
rank
*
rank
;
FBlas
::
setzero
(
2
*
rank
*
rank
,
rU
);
for
(
unsigned
int
l
=
0
;
l
<
rank
;
++
l
)
{
...
...
@@ -173,23 +173,25 @@ static void precompute(const MatrixKernelClass *const MatrixKernel, const FReal
{
// allocate
assert
(
K
[
idx
]
==
nullptr
);
K
[
idx
]
=
new
FReal
[
2
*
rank
*
nnodes
];
K
[
idx
]
=
new
FReal
[
2
*
rank
*
nnodes
]
{}
;
// set low rank
LowRank
[
idx
]
=
static_cast
<
int
>
(
rank
);
// (U Sigma)
for
(
unsigned
int
r
=
0
;
r
<
rank
;
++
r
)
for
(
unsigned
int
r
=
0
;
r
<
rank
;
++
r
)
{
FBlas
::
scal
(
aca_rank
,
S
[
r
],
phi
+
r
*
aca_rank
);
}
// Qu (U Sigma)
FBlas
::
gemm
(
nnodes
,
aca_rank
,
rank
,
FReal
(
1.
),
UU
,
nnodes
,
phi
,
aca_rank
,
K
[
idx
],
nnodes
);
delete
[]
phi
;
// Vt -> V and then Qu V
FReal
*
const
V
=
new
FReal
[
aca_rank
*
rank
];
for
(
unsigned
int
r
=
0
;
r
<
rank
;
++
r
)
FReal
*
const
V
=
new
FReal
[
aca_rank
*
rank
]
{}
;
for
(
unsigned
int
r
=
0
;
r
<
rank
;
++
r
)
{
FBlas
::
copy
(
aca_rank
,
VT
+
r
,
aca_rank
,
V
+
r
*
aca_rank
,
1
);
}
FBlas
::
gemm
(
nnodes
,
aca_rank
,
rank
,
FReal
(
1.
),
VV
,
nnodes
,
V
,
aca_rank
,
K
[
idx
]
+
rank
*
nnodes
,
nnodes
);
delete
[]
V
;
}
...
...
@@ -232,13 +234,15 @@ static void precompute(const MatrixKernelClass *const MatrixKernel, const FReal
// store
const
unsigned
int
idx
=
(
i
+
3
)
*
7
*
7
+
(
j
+
3
)
*
7
+
(
k
+
3
);
assert
(
K
[
idx
]
==
nullptr
);
K
[
idx
]
=
new
FReal
[
2
*
rank
*
nnodes
];
K
[
idx
]
=
new
FReal
[
2
*
rank
*
nnodes
]
{}
;
LowRank
[
idx
]
=
rank
;
for
(
unsigned
int
r
=
0
;
r
<
rank
;
++
r
)
FBlas
::
scal
(
nnodes
,
S
[
r
],
U
+
r
*
nnodes
);
for
(
unsigned
int
r
=
0
;
r
<
rank
;
++
r
){
FBlas
::
scal
(
nnodes
,
S
[
r
],
U
+
r
*
nnodes
);
}
FBlas
::
copy
(
rank
*
nnodes
,
U
,
K
[
idx
]);
for
(
unsigned
int
r
=
0
;
r
<
rank
;
++
r
)
FBlas
::
copy
(
nnodes
,
VT
+
r
,
nnodes
,
K
[
idx
]
+
rank
*
nnodes
+
r
*
nnodes
,
1
);
for
(
unsigned
int
r
=
0
;
r
<
rank
;
++
r
){
FBlas
::
copy
(
nnodes
,
VT
+
r
,
nnodes
,
K
[
idx
]
+
rank
*
nnodes
+
r
*
nnodes
,
1
);
}
elapsed_time
=
time
.
tacAndElapsed
();
overall_time
+=
elapsed_time
;
...
...
@@ -335,14 +339,16 @@ public:
// set permutation vector and indices
const
FInterpSymmetries
<
ORDER
>
Symmetries
;
for
(
int
i
=-
3
;
i
<=
3
;
++
i
)
for
(
int
j
=-
3
;
j
<=
3
;
++
j
)
for
(
int
i
=-
3
;
i
<=
3
;
++
i
)
{
for
(
int
j
=-
3
;
j
<=
3
;
++
j
)
{
for
(
int
k
=-
3
;
k
<=
3
;
++
k
)
{
const
unsigned
int
idx
=
((
i
+
3
)
*
7
+
(
j
+
3
))
*
7
+
(
k
+
3
);
pindices
[
idx
]
=
0
;
if
(
abs
(
i
)
>
1
||
abs
(
j
)
>
1
||
abs
(
k
)
>
1
)
pindices
[
idx
]
=
Symmetries
.
getPermutationArrayAndIndex
(
i
,
j
,
k
,
pvectors
[
idx
]);
}
}
}
// precompute 16 M2L operators
const
FReal
ReferenceCellWidth
=
FReal
(
2.0
);
...
...
@@ -394,8 +400,8 @@ class SymmetryHandler<FReal, ORDER, NON_HOMOGENEOUS>
public:
// permutation vectors and permutated indices
unsigned
int
pvectors
[
343
][
nnodes
];
unsigned
int
pindices
[
343
];
unsigned
int
pvectors
[
343
][
nnodes
]
{}
;
unsigned
int
pindices
[
343
]
{}
;
/** Constructor: with 16 small SVDs */
...
...
@@ -405,32 +411,34 @@ public:
:
TreeHeight
(
inTreeHeight
)
{
// init all 343 item to zero, because effectively only 16 exist
K
=
new
FReal
**
[
TreeHeight
];
LowRank
=
new
int
*
[
TreeHeight
];
K
=
new
FReal
**
[
TreeHeight
]
{}
;
LowRank
=
new
int
*
[
TreeHeight
]
{}
;
K
[
0
]
=
nullptr
;
K
[
1
]
=
nullptr
;
LowRank
[
0
]
=
nullptr
;
LowRank
[
1
]
=
nullptr
;
for
(
unsigned
int
l
=
2
;
l
<
TreeHeight
;
++
l
)
{
K
[
l
]
=
new
FReal
*
[
343
];
LowRank
[
l
]
=
new
int
[
343
];
for
(
unsigned
int
t
=
0
;
t
<
343
;
++
t
)
{
K
[
l
][
t
]
=
nullptr
;
LowRank
[
l
][
t
]
=
0
;
}
K
[
l
]
=
new
FReal
*
[
343
]
{}
;
LowRank
[
l
]
=
new
int
[
343
]
{}
;
//
for (unsigned int t=0; t<343; ++t) {
//
K[l][t] = nullptr;
//
LowRank[l][t] = 0;
//
}
}
// set permutation vector and indices
const
FInterpSymmetries
<
ORDER
>
Symmetries
;
for
(
int
i
=-
3
;
i
<=
3
;
++
i
)
for
(
int
j
=-
3
;
j
<=
3
;
++
j
)
for
(
int
i
=-
3
;
i
<=
3
;
++
i
)
{
for
(
int
j
=-
3
;
j
<=
3
;
++
j
)
{
for
(
int
k
=-
3
;
k
<=
3
;
++
k
)
{
const
unsigned
int
idx
=
((
i
+
3
)
*
7
+
(
j
+
3
))
*
7
+
(
k
+
3
);
pindices
[
idx
]
=
0
;
if
(
abs
(
i
)
>
1
||
abs
(
j
)
>
1
||
abs
(
k
)
>
1
)
pindices
[
idx
]
=
Symmetries
.
getPermutationArrayAndIndex
(
i
,
j
,
k
,
pvectors
[
idx
]);
}
}
}
// precompute 16 M2L operators at all levels having far-field interactions
FReal
CellWidth
=
RootCellWidth
/
FReal
(
2.
);
// at level 1
...
...
@@ -511,7 +519,7 @@ static void ComputeAndCompressAndStoreInBinaryFile(const MatrixKernelClass *cons
std
::
ios
::
out
|
std
::
ios
::
binary
|
std
::
ios
::
trunc
);
if
(
stream
.
good
())
{
stream
.
seekp
(
0
);
for
(
unsigned
int
idx
=
0
;
idx
<
343
;
++
idx
)
for
(
unsigned
int
idx
=
0
;
idx
<
343
;
++
idx
)
{
if
(
K
[
idx
]
!=
nullptr
)
{
// 1) write index
stream
.
write
(
reinterpret_cast
<
char
*>
(
&
idx
),
sizeof
(
int
));
...
...
@@ -524,7 +532,11 @@ static void ComputeAndCompressAndStoreInBinaryFile(const MatrixKernelClass *cons
stream
.
write
(
reinterpret_cast
<
char
*>
(
U
),
sizeof
(
FReal
)
*
rank
*
nnodes
);
stream
.
write
(
reinterpret_cast
<
char
*>
(
V
),
sizeof
(
FReal
)
*
rank
*
nnodes
);
}
}
else
throw
std
::
runtime_error
(
"File could not be opened to write"
);
}
}
else
{
throw
std
::
runtime_error
(
"File could not be opened to write"
);
}
stream
.
close
();
// write info
// std::cout << "Compressed M2L operators stored in binary file " << filename
...
...
@@ -577,7 +589,7 @@ void ReadFromBinaryFile(const FReal Epsilon, FReal* K[343], int LowRank[343])
istream
.
read
(
reinterpret_cast
<
char
*>
(
&
rank
),
sizeof
(
int
));
LowRank
[
idx
]
=
rank
;
// 3) read U and V (both: rank*nnodes * FReal)
K
[
idx
]
=
new
FReal
[
2
*
rank
*
nnodes
];
K
[
idx
]
=
new
FReal
[
2
*
rank
*
nnodes
]
{}
;
FReal
*
const
U
=
K
[
idx
];
FReal
*
const
V
=
K
[
idx
]
+
rank
*
nnodes
;
istream
.
read
(
reinterpret_cast
<
char
*>
(
U
),
sizeof
(
FReal
)
*
rank
*
nnodes
);
...
...
@@ -587,7 +599,10 @@ void ReadFromBinaryFile(const FReal Epsilon, FReal* K[343], int LowRank[343])
istream
.
read
(
reinterpret_cast
<
char
*>
(
&
_idx
),
sizeof
(
int
));
}
}
}
else
throw
std
::
runtime_error
(
"File could not be opened to read"
);
}
else
{
throw
std
::
runtime_error
(
"File could not be opened to read"
);
}
istream
.
close
();
}
...
...
Src/Kernels/Interpolation/FInterpSymmetries.hpp
View file @
c6d35276
...
...
@@ -42,13 +42,13 @@ class FInterpSymmetries
FInterpSymmetries
()
{
// permutations for 8 quadrants
unsigned
int
quads
[
8
][
nnodes
];
unsigned
int
quads
[
8
][
nnodes
]
{}
;
// permutations for 6 cones in quadrant (+++), 2 and 5 do not exist
unsigned
int
cones
[
8
][
nnodes
];
unsigned
int
cones
[
8
][
nnodes
]
{}
;
// set quads and cones permutations
unsigned
int
evn
[
ORDER
],
odd
[
ORDER
];
for
(
unsigned
int
o
=
0
;
o
<
ORDER
;
++
o
)
{
unsigned
int
evn
[
ORDER
]
{}
,
odd
[
ORDER
]
{}
;
for
(
unsigned
int
o
=
0
;
o
<
ORDER
;
++
o
)
{
evn
[
o
]
=
o
;
odd
[
o
]
=
ORDER
-
1
-
o
;}
...
...
Tests/Utils/testLapack.cpp
View file @
c6d35276
...
...
@@ -119,6 +119,7 @@ int main(int argc, char ** argv)
delete
[]
A
;
delete
[]
B
;
delete
[]
C
;
delete
[]
L
;
return
0
;
}
Tests/Utils/testLapackQR.cpp
View file @
c6d35276
...
...
@@ -116,6 +116,11 @@ int main(int argc, char ** argv)
if
(
INFO
!=
0
)
{
std
::
stringstream
stream
;
stream
<<
INFO
;
delete
[]
A
;
delete
[]
C
;
delete
[]
jpiv
;
delete
[]
TAU
;
delete
[]
WORK
;
throw
std
::
runtime_error
(
"get Q failed! INFO="
+
stream
.
str
());
}
double
tGETQ
=
timeGETQ
.
tacAndElapsed
();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment