Commit 541e8857 authored by Reinhard Prix's avatar Reinhard Prix
Browse files

ComputeFstat API: moved 'dFreq' argument from Compute() into Setup() function

- better for Resampling, as some workspace sizes depend on frequency
  resolution determining number of samples of zero-padded SRC-frame timeseries
- with fixed dFreq we can fix this and will later be able to re-use this across
  different compute() calls
- refs #1936, #535
Original: aa0d124ae22497ebeb426c905ab95d252fb0978e
parent bf0f9bb6
......@@ -177,6 +177,8 @@ typedef struct {
RankingStat_t RankingStatistic; /**< rank candidates according to F or BSGL */
BOOLEAN useResamp;
FstatMethodType FstatMethod;
UINT4 numFreqBins_FBand;
REAL8 dFreq;
} ConfigVariables;
......@@ -478,18 +480,6 @@ int main(int argc,char *argv[])
/* fixed time-offset between internalRefTime and refTime */
REAL8 DeltaTRefInt = XLALGPSDiff ( &(GV.internalRefTime), &(GV.searchRegion.refTime) ); // tRefInt - tRef
UINT4 numFreqBins_FBand = 1; // number of frequency-bins in the frequency-band used for resampling (1 if not using Resampling)
REAL8 dFreqResamp = 0; // frequency resolution used to allocate vector of F-stat values for resampling
if ( GV.useResamp ) // handle special resampling case, where we deal with a vector of F-stat values instead of one
{
if ( XLALUserVarWasSet(&uvar.dFreq) ) {
dFreqResamp = uvar.dFreq;
} else {
dFreqResamp = 1.0/(2*GV.Tspan);
}
numFreqBins_FBand = (UINT4) ( 1 + floor ( GV.searchRegion.fkdotBand[0] / dFreqResamp ) );
}
// ----- prepare timing info
REAL8 tic0, tic, toc, timeOfLastProgressUpdate = 0; // high-precision timing counters
timingInfo_t XLAL_INIT_DECL(timing); // timings of Fstatistic computation, transient Fstat-map, transient Bayes factor
......@@ -515,7 +505,7 @@ int main(int argc,char *argv[])
internalDopplerpos.refTime = GV.internalRefTime;
/* main function call: compute F-statistic for this template */
XLAL_CHECK_MAIN ( XLALComputeFstat ( &Fstat_res, GV.Fstat_in, &internalDopplerpos, dFreqResamp, numFreqBins_FBand, GV.Fstat_what) == XLAL_SUCCESS, XLAL_EFUNC );
XLAL_CHECK_MAIN ( XLALComputeFstat ( &Fstat_res, GV.Fstat_in, &internalDopplerpos, GV.numFreqBins_FBand, GV.Fstat_what) == XLAL_SUCCESS, XLAL_EFUNC );
/* if single-only flag is given, add +4 to F-statistic */
if ( uvar.SignalOnly ) {
......@@ -541,12 +531,12 @@ int main(int argc,char *argv[])
// main-loop: we simply loop the remaining body over all frequency-bins in the Fstat-vector,
// this way nothing needs to be changed! in the non-resampling case, this loop iterates only
// once, so nothing is changed ...
for ( UINT4 iFreq = 0; iFreq < numFreqBins_FBand; iFreq ++ )
for ( UINT4 iFreq = 0; iFreq < GV.numFreqBins_FBand; iFreq ++ )
{
/* collect data on current 'Fstat-candidate' */
thisFCand.doppler = dopplerpos; // use 'original' dopplerpos @ refTime !
thisFCand.doppler.fkdot[0] += iFreq * dFreqResamp; // this only does something for the resampling post-loop over frequency-bins, 0 otherwise ...
thisFCand.doppler.fkdot[0] += iFreq * GV.dFreq; // this only does something for the resampling post-loop over frequency-bins, 0 otherwise ...
thisFCand.twoF = Fstat_res->twoF[iFreq];
if (GV.Fstat_what & FSTATQ_2F_PER_DET) {
thisFCand.numDetectors = Fstat_res->numDetectors;
......@@ -770,10 +760,10 @@ int main(int argc,char *argv[])
/* if requested: output timings into timing-file */
if ( uvar.outputTiming )
{
REAL8 num_templates = numTemplates * numFreqBins_FBand; // 'templates' now refers to number of 'frequency-bands' in resampling case
REAL8 num_templates = numTemplates * GV.numFreqBins_FBand; // 'templates' now refers to number of 'frequency-bands' in resampling case
timing.NSFTs = GV.NSFTs;
timing.NFreq = (UINT4) ( 1 + floor ( GV.searchRegion.fkdotBand[0] / uvar.dFreq ) );
timing.NFreq = (UINT4) ( 1 + floor ( GV.searchRegion.fkdotBand[0] / GV.dFreq ) );
timing.FstatMethod = GV.FstatMethod;
......@@ -1372,6 +1362,17 @@ InitFstat ( ConfigVariables *cfg, const UserInput_t *uvar )
assumeSqrtSX = NULL;
}
if ( XLALUserVarWasSet ( &uvar->dFreq) ) {
cfg->dFreq = uvar->dFreq;
} else {
cfg->dFreq = 1.0/(2*cfg->Tspan);
}
if ( cfg->useResamp ) { // handle special resampling case, where we deal with a vector of F-stat values instead of one
cfg->numFreqBins_FBand = (UINT4) ( 1 + floor ( cfg->searchRegion.fkdotBand[0] / cfg->dFreq ) );
} else {
cfg->numFreqBins_FBand = 1; // number of frequency-bins in the frequency-band used for resampling (1 if not using Resampling)
}
PulsarParamsVector *injectSources = NULL;
MultiNoiseFloor *injectSqrtSX = NULL;
FstatOptionalArgs optionalArgs = FstatOptionalArgsDefaults;
......@@ -1383,7 +1384,7 @@ InitFstat ( ConfigVariables *cfg, const UserInput_t *uvar )
optionalArgs.assumeSqrtSX = assumeSqrtSX;
optionalArgs.FstatMethod = cfg->FstatMethod;
XLAL_CHECK ( (cfg->Fstat_in = XLALCreateFstatInput( catalog, fCoverMin, fCoverMax, cfg->ephemeris, &optionalArgs )) != NULL, XLAL_EFUNC );
XLAL_CHECK ( (cfg->Fstat_in = XLALCreateFstatInput( catalog, fCoverMin, fCoverMax, cfg->dFreq, cfg->ephemeris, &optionalArgs )) != NULL, XLAL_EFUNC );
XLALDestroySFTCatalog(catalog);
cfg->Fstat_what = FSTATQ_2F; // always calculate multi-detector 2F
......
......@@ -1440,7 +1440,7 @@ int MAIN( int argc, char *argv[]) {
if (doComputeFstats) { /* if first time through fine grid fdots loop */
timeFstatStart = XLALGetTimeOfDay();
const int retn = XLALComputeFstat(&Fstat_res, Fstat_in_vec->data[k], &thisPoint, dFreqStack, binsFstat1, Fstat_what);
const int retn = XLALComputeFstat(&Fstat_res, Fstat_in_vec->data[k], &thisPoint, binsFstat1, Fstat_what);
if ( retn != XLAL_SUCCESS ) {
XLALPrintError ("%s: XLALComputeFstat() failed with errno=%d\n", __func__, xlalErrno );
return xlalErrno;
......@@ -2053,7 +2053,8 @@ void SetUpSFTs( LALStatus *status, /**< pointer to LALStatus structure */
}
/* ----- create Fstat input data struct ----- */
(*p_Fstat_in_vec)->data[k] = XLALCreateFstatInput ( &catalogSeq.data[k], freqmin, freqmax, in->edat, &optionalArgs );
(*p_Fstat_in_vec)->data[k] = XLALCreateFstatInput ( &catalogSeq.data[k], freqmin, freqmax, in->dFreqStack, in->edat, &optionalArgs );
if ( (*p_Fstat_in_vec)->data[k] == NULL ) {
XLALPrintError("%s: XLALCreateFstatInput() failed with errno=%d", __func__, xlalErrno);
ABORT ( status, HIERARCHICALSEARCH_EXLAL, HIERARCHICALSEARCH_MSGEXLAL );
......
......@@ -192,7 +192,7 @@ int XLALComputeExtraStatsSemiCoherent ( RecalcStatsComponents *recalcStats, /**
XLAL_CHECK ( XLALExtrapolatePulsarSpins( dopplerParams_temp.fkdot, dopplerParams->fkdot, deltaTau ) == XLAL_SUCCESS, XLAL_EFUNC, "XLALExtrapolatePulsarSpins() failed." );
/* recompute multi-detector Fstat and atoms */
XLAL_CHECK ( XLALComputeFstat(&Fstat_res, recalcParams->Fstat_in_vec->data[k], &dopplerParams_temp, 0.0, 1, FSTATQ_2F | FSTATQ_2F_PER_DET) == XLAL_SUCCESS, XLAL_EFUNC, "XLALComputeFstat() failed with errno=%d", xlalErrno );
XLAL_CHECK ( XLALComputeFstat(&Fstat_res, recalcParams->Fstat_in_vec->data[k], &dopplerParams_temp, 1, FSTATQ_2F | FSTATQ_2F_PER_DET) == XLAL_SUCCESS, XLAL_EFUNC, "XLALComputeFstat() failed with errno=%d", xlalErrno );
sumTwoF += Fstat_res->twoF[0]; /* sum up multi-detector Fstat for this segment*/
......
......@@ -194,6 +194,7 @@ typedef struct {
UINT4 Dterms; /**< size of Dirichlet kernel for Fstat calculation */
REAL8 dopplerMax; /**< extra sft wings for doppler motion */
SSBprecision SSBprec; /**< SSB transform precision */
REAL8 dFreqStack; /**< frequency resolution of Fstat calculation */
} UsefulStageVariables;
......@@ -321,7 +322,6 @@ int MAIN( int argc, char *argv[]) {
/* number of stacks -- not necessarily same as uvar_nStacks! */
UINT4 nStacks;
REAL8 dFreqStack; /* frequency resolution of Fstat calculation */
REAL8 df1dot, df1dotRes; /* coarse grid resolution in spindown */
UINT4 nf1dot, nf1dotRes; /* coarse and fine grid number of spindown values */
......@@ -693,10 +693,10 @@ int MAIN( int argc, char *argv[]) {
/* set Fstat calculation frequency resolution
-- default is 1/tstack */
if ( LALUserVarWasSet(&uvar_dFreq) ) {
dFreqStack = uvar_dFreq;
usefulParams.dFreqStack = uvar_dFreq;
}
else {
dFreqStack = 1.0/tStack;
usefulParams.dFreqStack = 1.0/tStack;
}
/* set Fstat spindown resolution
......@@ -772,7 +772,7 @@ int MAIN( int argc, char *argv[]) {
/* print debug info about stacks */
LogPrintf(LOG_DETAIL, "1st stage params: Nstacks = %d, Tstack = %.0fsec, dFreq = %eHz, Tobs = %.0fsec\n",
nStacks, tStack, dFreqStack, tObs);
nStacks, tStack, usefulParams.dFreqStack, tObs);
if ( weightsNoise ) {
for (k = 0; k < nStacks; k++) {
LogPrintf(LOG_DETAIL, "Stack %d (GPS start time = %d, Noise weight = %f )\n ",
......@@ -971,7 +971,7 @@ int MAIN( int argc, char *argv[]) {
/* calculate number of bins for fstat overhead */
freqHighest = usefulParams.spinRange_midTime.fkdot[0] + usefulParams.spinRange_midTime.fkdotBand[0];
ComputeNumExtraBins(&status, &semiCohPar, 0, freqHighest, dFreqStack);
ComputeNumExtraBins(&status, &semiCohPar, 0, freqHighest, usefulParams.dFreqStack);
/* conservative estimate */
/* extraBinsSky = (UINT4)(0.5 * LAL_SQRT2 * VTOT */
......@@ -982,19 +982,19 @@ int MAIN( int argc, char *argv[]) {
caused by the residual spindown. The reference time for the spindown is the midtime,
so relevant interval is Tobs/2 and largest possible value of residual spindown is
(number of residual spindowns -1)*resolution in residual spindowns */
extraBinsfdot = (UINT4)(tObs * (nf1dotRes - 1) * df1dotRes / dFreqStack + 0.5);
extraBinsfdot = (UINT4)(tObs * (nf1dotRes - 1) * df1dotRes / usefulParams.dFreqStack + 0.5);
semiCohPar.extraBinsFstat += extraBinsfdot;
/* allocate fstat memory */
binsFstatSearch = (UINT4)(usefulParams.spinRange_midTime.fkdotBand[0]/dFreqStack + 1e-6) + 1;
binsFstatSearch = (UINT4)(usefulParams.spinRange_midTime.fkdotBand[0]/usefulParams.dFreqStack + 1e-6) + 1;
binsFstat1 = binsFstatSearch + 2*semiCohPar.extraBinsFstat;
for (k = 0; k < nStacks; k++) {
/* careful--the epoch here is not the reference time for f0! */
fstatVector.data[k].epoch = startTstack->data[k];
fstatVector.data[k].deltaF = dFreqStack;
fstatVector.data[k].f0 = usefulParams.spinRange_midTime.fkdot[0] - semiCohPar.extraBinsFstat * dFreqStack;
fstatVector.data[k].deltaF = usefulParams.dFreqStack;
fstatVector.data[k].f0 = usefulParams.spinRange_midTime.fkdot[0] - semiCohPar.extraBinsFstat * usefulParams.dFreqStack;
if (fstatVector.data[k].data == NULL) {
fstatVector.data[k].data = LALCalloc( 1, alloc_len = sizeof(REAL4Sequence));
XLAL_CHECK( fstatVector.data[k].data != NULL, XLAL_ENOMEM, "Failed to allocate memory LALCalloc ( 1, %d )\n", alloc_len );
......@@ -1039,7 +1039,7 @@ int MAIN( int argc, char *argv[]) {
thisPoint.fkdot[0] = fstatVector.data[k].f0;
/* thisPoint.fkdot[0] = usefulParams.spinRange_midTime.fkdot[0]; */
const int retn = XLALComputeFstat(&Fstat_res, Fstat_in_vec->data[k], &thisPoint, fstatVector.data[0].deltaF, binsFstat1, Fstat_what);
const int retn = XLALComputeFstat(&Fstat_res, Fstat_in_vec->data[k], &thisPoint, binsFstat1, Fstat_what);
if ( retn != XLAL_SUCCESS ) {
XLALPrintError ("%s: XLALComputeFstat() failed with errno=%d\n", __func__, xlalErrno );
return xlalErrno;
......@@ -1436,7 +1436,7 @@ void SetUpSFTs( LALStatus *status, /**< pointer to LALStatus structure */
for (k = 0; k < in->nStacks; k++) {
/* create Fstat input data struct for Fstat-computation */
(*p_Fstat_in_vec)->data[k] = XLALCreateFstatInput( &catalogSeq.data[k], fMin, fMax, in->edat, &optionalArgs );
(*p_Fstat_in_vec)->data[k] = XLALCreateFstatInput( &catalogSeq.data[k], fMin, fMax, in->dFreqStack, in->edat, &optionalArgs );
if ( (*p_Fstat_in_vec)->data[k] == NULL ) {
XLALPrintError("%s: XLALCreateFstatInput() failed with errno=%d", __func__, xlalErrno);
ABORT ( status, HIERARCHICALSEARCH_EXLAL, HIERARCHICALSEARCH_MSGEXLAL );
......
......@@ -48,6 +48,7 @@ typedef struct {
const EphemerisData *ephemerides; // Ephemerides for the time-span of the SFTs
SSBprecision SSBprec; // Barycentric transformation precision
FstatMethodType FstatMethod; // Method to use for computing the F-statistic
REAL8 dFreq; // Requested spacing of \f$\mathcal{F}\f$-statistic frequency bins.
} FstatInput_Common;
// Input data specific to F-statistic methods
......@@ -262,6 +263,7 @@ XLALCreateFstatInput ( const SFTCatalog *SFTcatalog, ///< [in] Catalog of SFT
///< The \c locator field of each ::SFTDescriptor must be \c !=NULL for SFT loading, and \c ==NULL for SFT generation.
const REAL8 minCoverFreq, ///< [in] Minimum instantaneous frequency which will be covered over the SFT time span.
const REAL8 maxCoverFreq, ///< [in] Maximum instantaneous frequency which will be covered over the SFT time span.
const REAL8 dFreq, ///< [in] Requested spacing of \f$\mathcal{F}\f$-statistic frequency bins.
const EphemerisData *ephemerides, ///< [in] Ephemerides for the time-span of the SFTs.
const FstatOptionalArgs *optionalArgs ///< [in] Optional 'advanced-level' and method-specific extra arguments; NULL: use defaults from FstatOptionalArgsDefaults.
)
......@@ -280,6 +282,7 @@ XLALCreateFstatInput ( const SFTCatalog *SFTcatalog, ///< [in] Catalog of SFT
XLAL_CHECK_NULL ( isfinite(maxCoverFreq) && maxCoverFreq > 0, XLAL_EINVAL );
XLAL_CHECK_NULL ( maxCoverFreq > minCoverFreq, XLAL_EINVAL );
XLAL_CHECK_NULL ( ephemerides != NULL, XLAL_EINVAL );
XLAL_CHECK_NULL ( dFreq > 0, XLAL_EINVAL);
// handle optional arguments, if given
const FstatOptionalArgs *optArgs;
......@@ -322,6 +325,7 @@ XLALCreateFstatInput ( const SFTCatalog *SFTcatalog, ///< [in] Catalog of SFT
XLAL_ERROR_NULL ( XLAL_EINVAL, "Received invalid Fstat method enum '%d'\n", optArgs->FstatMethod );
}
common->FstatMethod = optArgs->FstatMethod;
common->dFreq = dFreq;
// Determine the time baseline of an SFT
const REAL8 Tsft = 1.0 / SFTcatalog->data[0].header.deltaF;
......@@ -542,7 +546,6 @@ int
XLALComputeFstat ( FstatResults **Fstats, ///< [in/out] Address of a pointer to a #FstatResults results structure; if \c NULL, allocate here.
FstatInput *input, ///< [in] Input data structure created by one of the setup functions.
const PulsarDopplerParams *doppler, ///< [in] Doppler parameters, including starting frequency, at which to compute \f$2\mathcal{F}\f$
const REAL8 dFreq, ///< [in] Required spacing in frequency between each \f$\mathcal{F}\f$-statistic.
const UINT4 numFreqBins, ///< [in] Number of frequencies at which the \f$2\mathcal{F}\f$ are to be computed.
const FstatQuantities whatToCompute ///< [in] Bit-field of which \f$\mathcal{F}\f$-statistic quantities to compute.
)
......@@ -553,7 +556,6 @@ XLALComputeFstat ( FstatResults **Fstats, ///< [in/out] Address of a pointer
XLAL_CHECK ( input->common != NULL, XLAL_EINVAL, "'input' has not yet been set up");
XLAL_CHECK ( doppler != NULL, XLAL_EINVAL);
XLAL_CHECK ( doppler->asini >= 0, XLAL_EINVAL);
XLAL_CHECK ( dFreq > 0 || (numFreqBins == 1 && dFreq >= 0), XLAL_EINVAL);
XLAL_CHECK ( numFreqBins > 0, XLAL_EINVAL);
XLAL_CHECK ( 0 < whatToCompute && whatToCompute < FSTATQ_LAST, XLAL_EINVAL);
......@@ -640,7 +642,7 @@ XLALComputeFstat ( FstatResults **Fstats, ///< [in/out] Address of a pointer
// Initialise result struct parameters
(*Fstats)->doppler = *doppler;
(*Fstats)->dFreq = dFreq;
(*Fstats)->dFreq = common->dFreq;
(*Fstats)->numFreqBins = numFreqBins;
(*Fstats)->numDetectors = numDetectors;
memset ( (*Fstats)->detectorNames, 0, sizeof((*Fstats)->detectorNames) );
......
......@@ -297,7 +297,8 @@ MultiFstatAtomVector* XLALCreateMultiFstatAtomVector ( const UINT4 length );
void XLALDestroyMultiFstatAtomVector ( MultiFstatAtomVector *atoms );
FstatInput *
XLALCreateFstatInput ( const SFTCatalog *SFTcatalog, const REAL8 minCoverFreq, const REAL8 maxCoverFreq, const EphemerisData *ephemerides, const FstatOptionalArgs *optionalArgs );
XLALCreateFstatInput ( const SFTCatalog *SFTcatalog, const REAL8 minCoverFreq, const REAL8 maxCoverFreq, const REAL8 dFreq,
const EphemerisData *ephemerides, const FstatOptionalArgs *optionalArgs );
const MultiLALDetector* XLALGetFstatInputDetectors ( const FstatInput* input );
const MultiLIGOTimeGPSVector* XLALGetFstatInputTimestamps ( const FstatInput* input );
......@@ -308,7 +309,7 @@ const MultiDetectorStateSeries* XLALGetFstatInputDetectorStates ( const FstatInp
SWIGLAL(INOUT_STRUCTS(FstatResults**, Fstats));
#endif
int XLALComputeFstat ( FstatResults **Fstats, FstatInput *input, const PulsarDopplerParams *doppler,
const REAL8 dFreq, const UINT4 numFreqBins, const FstatQuantities whatToCompute );
const UINT4 numFreqBins, const FstatQuantities whatToCompute );
void XLALDestroyFstatInput ( FstatInput* input );
void XLALDestroyFstatResults ( FstatResults* Fstats );
......
......@@ -159,7 +159,7 @@ main ( int argc, char *argv[] )
XLAL_CHECK ( (inputs = XLALCreateFstatInputVector ( uvar->numSegments )) != NULL, XLAL_EFUNC );
for ( INT4 l = 0; l < uvar->numSegments; l ++ )
{
XLAL_CHECK ( (inputs->data[l] = XLALCreateFstatInput ( catalogs[l], minCoverFreq, maxCoverFreq, ephem, &optionalArgs )) != NULL, XLAL_EFUNC );
XLAL_CHECK ( (inputs->data[l] = XLALCreateFstatInput ( catalogs[l], minCoverFreq, maxCoverFreq, dFreq, ephem, &optionalArgs )) != NULL, XLAL_EFUNC );
}
// ----- compute Fstatistics over segments
......@@ -172,12 +172,12 @@ main ( int argc, char *argv[] )
{
// call it once to initialize buffering, don't count this time
REAL8 tic = XLALGetTimeOfDay();
XLAL_CHECK ( XLALComputeFstat ( &results[l], inputs->data[l], &Doppler, dFreq, uvar->numFreqBins, whatToCompute ) == XLAL_SUCCESS, XLAL_EFUNC );
XLAL_CHECK ( XLALComputeFstat ( &results[l], inputs->data[l], &Doppler, uvar->numFreqBins, whatToCompute ) == XLAL_SUCCESS, XLAL_EFUNC );
REAL8 toc = XLALGetTimeOfDay();
tauFSumUnbuffered += ( toc - tic );
// now call it with full buffering to get converged runtime per template (assuming many templates per skypoint or per binary params)
tic = XLALGetTimeOfDay();
XLAL_CHECK ( XLALComputeFstat ( &results[l], inputs->data[l], &Doppler, dFreq, uvar->numFreqBins, whatToCompute ) == XLAL_SUCCESS, XLAL_EFUNC );
XLAL_CHECK ( XLALComputeFstat ( &results[l], inputs->data[l], &Doppler, uvar->numFreqBins, whatToCompute ) == XLAL_SUCCESS, XLAL_EFUNC );
toc = XLALGetTimeOfDay();
tauFSumBuffered += (toc - tic);
} // for l < numSegments
......
......@@ -160,7 +160,7 @@ main ( int argc, char *argv[] )
continue;
}
optionalArgs.FstatMethod = iMethod;
XLAL_CHECK ( (input[iMethod] = XLALCreateFstatInput ( catalog, minCoverFreq, maxCoverFreq, ephem, &optionalArgs )) != NULL, XLAL_EFUNC );
XLAL_CHECK ( (input[iMethod] = XLALCreateFstatInput ( catalog, minCoverFreq, maxCoverFreq, dFreq, ephem, &optionalArgs )) != NULL, XLAL_EFUNC );
}
FstatQuantities whatToCompute = (FSTATQ_2F | FSTATQ_FAFB);
......@@ -182,7 +182,7 @@ main ( int argc, char *argv[] )
firstMethod = iMethod;
}
XLAL_CHECK ( XLALComputeFstat ( &results[iMethod], input[iMethod], &Doppler, dFreq, numFreqBins, whatToCompute ) == XLAL_SUCCESS, XLAL_EFUNC );
XLAL_CHECK ( XLALComputeFstat ( &results[iMethod], input[iMethod], &Doppler, numFreqBins, whatToCompute ) == XLAL_SUCCESS, XLAL_EFUNC );
if ( lalDebugLevel & LALINFOBIT )
{
......
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