5B6D6UREX556XOAM75B3WOIGE6DT43HIS6JOVJYNXUT3FSZDUPBAC
3QWZBYJLGJFSLSL2ZG4JVCSUGF73TQJNPHV3MRMIIBUA7XNUU7NAC
WNYN6LX3FYXXW3TKGRA7GRLFMIR4YXQMFAWJCB76QAXLY6JR4Q7AC
ANVJBTFKJDVQBSYMHQ455IQVAOCD3YVYZLDH5K2BS67UTSF5JNBQC
PY277W4CPZCB5NTENNXI4TNA7IFBOAQA2ZS7AGVTNCFSXJXKPPGAC
IVHURSHY4636OGIF3PNDO5CWOVRLJ75M4LP65J6I2E6KAM4QKF4AC
HVV73EHEMEFVCPS75XZAFUGWOY7LBYOJOIDNHJUK4MB5QTS3CTJQC
722HZ7UFINNE3YKSYKP2NHZ5XEG5QQLQHSKC7PREJZR3EX6RDYUAC
U77PE56ICORZNQW33NXGSEMW7GDHCSSZ4EXB6OHBJSHEG6WHYSSQC
TY6WZ2R2THWDIQCTDZIFSX3S2SNSC67GUFE7S4TFXLRQNP7SNUBAC
PU3YB5FX7NK65435O3H3AHCTUDHFRW7IOARLJJ4NBFG3GTIYVLCQC
WV2J4VRLSW42SGIRDLF326LLC7L2ABAMSZ3YKABS532K27U3X3CAC
FS7Q6TUHBK5WSRDC3TM6KV2BPGWATRBLDHFGEJ72BR3FRDEOC3WAC
JN2TPHENEBIY2OE5FRCQ2E6QCL6FPVHJHUCP4UODD6DITRVV2LIQC
BVR7DVINVPQG7PA6Z7QYVYNQ43YZL7XCC6AOMSMWMGAAB2Q43STAC
ZARZZPSISIOCXZOWNJQMMQSQPXFSZLDDIDAFY35X2GV37RBB7WUAC
M5R6KQLXLGYSVKHVAX5AJKD6NYE6IM5Z6WVTR3BTKPJDNNKF3ARAC
EHF2P5PKVTMAUL5R5QSZ3DS3VLE7Z6SHJTCZAGRBTQ66Y7HZKNYQC
WZPFZTNOJMA2ANRX22OL6QUCU6KUWSZVQJSL46KKBEB37MWRVNNAC
T3TZRPPAIA24I3YL3JFB4XEAYCWU3HJAJUCF7NNIFMP4I5X4SM5QC
XU5HOJREK4XY4NBCJINLZPKQNKSYOLUDTWR47REFSNQKDOSNDXLQC
IV3MLV677VWS7GLESBQZ4HRFNW5TBFBIGIBWXTCHAER3FQ7VG3GQC
KCIWCVZOHG44WBOLKI2XK33WPHPRI5FWCETF4AOGTPZISKCW3CLQC
VMCDMDXKME66ESRMB3PYSUZZH2XG2GIQMEOEKRH33WGCEBPXTWUQC
UZAKARMGORRQG733ZUPJOEGL5FG243I32NCC2SRSFDCZKUQ5A52QC
WASO7G5FJXRXWNH2U2FLUNEKU6VE63OI3HUYP64BVD4LMD6KE7OQC
BJDGFYBMECTJG7BHLNHLSCUCBVYHAY6OGY37FIJP6JDGNDXQNQVAC
W4XMGPEHBCV6AAPJBI4SSEMCDB6KKCGRUC2X2F5YLBY22OR3ICPAC
X7A7HYGU2NPWCZCG5SYFD4NINYDP6UYWFFE3WP6YNLSDIABG2KJAC
GKKJ75HX2ERLVBZVE2CUB6T3J2SUT7R3UKEKTEYNOG43ZKX6X5MQC
} else if (group.grouptype == CCTK_SCALAR) {
assert(group.vartype == CCTK_VARIABLE_REAL);
assert(group.disttype == CCTK_DISTRIB_CONSTANT);
assert(group.dim == 0);
globaldata.scalargroupdata.at(gi) =
make_unique<GHExt::GlobalData::ScalarGroupData>();
GHExt::GlobalData::ScalarGroupData &scalargroupdata =
*globaldata.scalargroupdata.at(gi);
scalargroupdata.groupindex = gi;
scalargroupdata.firstvarindex = CCTK_FirstVarIndexI(gi);
scalargroupdata.numvars = group.numvars;
scalargroupdata.do_checkpoint = get_group_checkpoint_flag(gi);
scalargroupdata.do_restrict = get_group_restrict_flag(gi);
assert(group.grouptype == CCTK_SCALAR);
assert(group.vartype == CCTK_VARIABLE_REAL);
assert(group.disttype == CCTK_DISTRIB_CONSTANT);
assert(group.dim == 0);
// Set up dynamic data
scalargroupdata.dimension = 0;
scalargroupdata.activetimelevels = 1;
// Allocate data
scalargroupdata.data.resize(group.numtimelevels);
scalargroupdata.valid.resize(group.numtimelevels);
for (int tl = 0; tl < int(scalargroupdata.data.size()); ++tl) {
scalargroupdata.data.at(tl).resize(scalargroupdata.numvars);
why_valid_t why([] { return "SetupGlobals"; });
scalargroupdata.valid.at(tl).resize(scalargroupdata.numvars, why);
for (int vi = 0; vi < scalargroupdata.numvars; ++vi) {
// TODO: decide that valid_bnd == false always and rely on
// initialization magic?
valid_t valid;
valid.valid_int = false;
valid.valid_outer = true;
valid.valid_ghosts = true;
scalargroupdata.valid.at(tl).at(vi).set(valid,
[] { return "SetupGlobals"; });
// TODO: make poison_invalid and check_invalid virtual members of
// CommonGroupData
poison_invalid(scalargroupdata, vi, tl);
check_valid(scalargroupdata, vi, tl, [] { return "SetupGlobals"; });
}
}
} else {
assert(group.grouptype == CCTK_ARRAY);
assert(group.vartype == CCTK_VARIABLE_REAL);
assert(group.disttype == CCTK_DISTRIB_CONSTANT);
assert(group.dim > 0 && group.dim <= dim);
globaldata.arraygroupdata.at(gi) =
make_unique<GHExt::GlobalData::ArrayGroupData>();
GHExt::GlobalData::ArrayGroupData &arraygroupdata =
*globaldata.arraygroupdata.at(gi);
arraygroupdata.groupindex = gi;
arraygroupdata.firstvarindex = CCTK_FirstVarIndexI(gi);
arraygroupdata.numvars = group.numvars;
arraygroupdata.do_checkpoint = get_group_checkpoint_flag(gi);
arraygroupdata.do_restrict = get_group_restrict_flag(gi);
globaldata.scalargroupdata.at(gi) =
make_unique<GHExt::GlobalData::ScalarGroupData>();
GHExt::GlobalData::ScalarGroupData &scalargroupdata =
*globaldata.scalargroupdata.at(gi);
scalargroupdata.groupindex = gi;
scalargroupdata.firstvarindex = CCTK_FirstVarIndexI(gi);
scalargroupdata.numvars = group.numvars;
scalargroupdata.do_checkpoint = get_group_checkpoint_flag(gi);
scalargroupdata.do_restrict = get_group_restrict_flag(gi);
CCTK_INT const *const *const sz = CCTK_GroupSizesI(gi);
arraygroupdata.array_size = 1;
for (int d = 0; d < group.dim; ++d) {
arraygroupdata.array_size = arraygroupdata.array_size * *sz[d];
}
// Allocate data
scalargroupdata.data.resize(group.numtimelevels);
scalargroupdata.valid.resize(group.numtimelevels);
for (int tl = 0; tl < int(scalargroupdata.data.size()); ++tl) {
scalargroupdata.data.at(tl).resize(scalargroupdata.numvars);
why_valid_t why([] { return "SetupGlobals"; });
scalargroupdata.valid.at(tl).resize(scalargroupdata.numvars, why);
for (int vi = 0; vi < scalargroupdata.numvars; ++vi) {
// TODO: decide that valid_bnd == false always and rely on
// initialization magic?
valid_t valid;
valid.valid_int = false;
valid.valid_outer = true;
valid.valid_ghosts = true;
scalargroupdata.valid.at(tl).at(vi).set(valid,
[] { return "SetupGlobals"; });
// Set up dynamic data
arraygroupdata.dimension = group.dim;
arraygroupdata.activetimelevels = 1;
for (int d = 0; d < group.dim; ++d) {
arraygroupdata.lsh[d] = *sz[d];
arraygroupdata.ash[d] = *sz[d];
arraygroupdata.gsh[d] = *sz[d];
arraygroupdata.nghostzones[d] = 0;
arraygroupdata.lbnd[d] = 0;
arraygroupdata.ubnd[d] = *sz[d]-1;
arraygroupdata.bbox[2*d] = arraygroupdata.bbox[2*d+1] = 1;
}
// Allocate data
arraygroupdata.data.resize(group.numtimelevels);
arraygroupdata.valid.resize(group.numtimelevels);
for (int tl = 0; tl < int(arraygroupdata.data.size()); ++tl) {
arraygroupdata.data.at(tl).resize(arraygroupdata.numvars*arraygroupdata.array_size);
why_valid_t why([] { return "SetupGlobals"; });
arraygroupdata.valid.at(tl).resize(arraygroupdata.numvars, why);
for (int vi = 0; vi < arraygroupdata.numvars; ++vi) {
// TODO: decide that valid_bnd == false always and rely on
// initialization magic?
valid_t valid;
valid.valid_int = false;
valid.valid_outer = true;
valid.valid_ghosts = true;
arraygroupdata.valid.at(tl).at(vi).set(valid,
[] { return "SetupGlobals"; });
// TODO: make poison_invalid and check_invalid virtual members of
// CommonGroupData
poison_invalid(scalargroupdata, vi, tl);
check_valid(scalargroupdata, vi, tl, [] { return "SetupGlobals"; });
// TODO: make poison_invalid and check_invalid virtual members of
// CommonGroupData
poison_invalid(arraygroupdata, vi, tl);
check_valid(arraygroupdata, vi, tl, [] { return "SetupGlobals"; });
}
YAML::Emitter &
operator<<(YAML::Emitter &yaml,
const GHExt::GlobalData::ArrayGroupData &arraygroupdata) {
yaml << YAML::LocalTag("arraygroupdata-1.0.0");
yaml << YAML::BeginMap;
yaml << YAML::Key << "commongroupdata" << YAML::Value
<< (GHExt::CommonGroupData)arraygroupdata;
yaml << YAML::Key << "data" << YAML::Value << arraygroupdata.data;
yaml << YAML::EndMap;
return yaml;
}
}
return 0;
}
int GroupDynamicData(const cGH *cctkGH, int gi, cGroupDynamicData *data) {
// Return values:
// 0 for success
// -1 if given pointer to data structure is NULL
// -3 if given GH pointer is invalid
// (-77 if group has zero variables)
// -78 if group does not exist
if (not cctkGH)
return -3;
if (not(gi >= 0 and gi < CCTK_NumGroups()))
return -78;
if (not data)
return -1;
cGroup group;
int ierr = CCTK_GroupData(gi, &group);
assert(!ierr);
if (group.grouptype != CCTK_SCALAR and group.grouptype != CCTK_ARRAY) {
data->dim = group.dim;
data->lsh = cctkGH->cctk_lsh;
data->ash = cctkGH->cctk_ash;
data->gsh = cctkGH->cctk_gsh;
data->lbnd = cctkGH->cctk_lbnd;
data->ubnd = cctkGH->cctk_ubnd;
data->bbox = cctkGH->cctk_bbox;
data->nghostzones = cctkGH->cctk_nghostzones;
data->activetimelevels = CCTK_ActiveTimeLevelsGI(cctkGH, gi);
} else if (group.grouptype == CCTK_SCALAR) {
GHExt::GlobalData &globaldata = ghext->globaldata;
GHExt::GlobalData::ScalarGroupData &scalargroupdata =
*globaldata.scalargroupdata.at(gi);
data->dim = scalargroupdata.dimension;
data->lsh = scalargroupdata.lsh;
data->ash = scalargroupdata.ash;
data->gsh = scalargroupdata.gsh;
data->lbnd = scalargroupdata.lbnd;
data->ubnd = scalargroupdata.ubnd;
data->bbox = scalargroupdata.bbox;
data->nghostzones = scalargroupdata.nghostzones;
data->activetimelevels = scalargroupdata.activetimelevels;
} else { // CCTK_ARRAY
GHExt::GlobalData &globaldata = ghext->globaldata;
GHExt::GlobalData::ArrayGroupData &arraygroupdata =
*globaldata.arraygroupdata.at(gi);
data->dim = arraygroupdata.dimension;
data->lsh = arraygroupdata.lsh;
data->ash = arraygroupdata.ash;
data->gsh = arraygroupdata.gsh;
data->lbnd = arraygroupdata.lbnd;
data->ubnd = arraygroupdata.ubnd;
data->bbox = arraygroupdata.bbox;
data->nghostzones = arraygroupdata.nghostzones;
data->activetimelevels = arraygroupdata.activetimelevels;
ArrayGroupData() {
dimension = -1;
activetimelevels = -1;
for (int d = 0; d < dim; d++) {
lsh[d] = -1;
ash[d] = -1;
gsh[d] = -1;
lbnd[d] = -1;
ubnd[d] = -1;
bbox[2*d] = bbox[2*d+1] = -1;
nghostzones[d] = -1;
}
}
friend YAML::Emitter &operator<<(YAML::Emitter &yaml,
const ArrayGroupData &arraygroupdata);
};
// TODO: right now this is sized for the total number of groups
vector<unique_ptr<ArrayGroupData> > arraygroupdata; // [group index]
}
// Ensure grid arrays are valid
void error_if_invalid(const GHExt::GlobalData::ArrayGroupData &groupdata,
int vi, int tl, const valid_t &required,
const function<string()> &msg) {
const valid_t &have = groupdata.valid.at(tl).at(vi).get();
if (CCTK_BUILTIN_EXPECT((required & ~have).valid_any(), false))
CCTK_VERROR("%s: Grid array \"%s\" is invalid on time level %d; "
"required %s, found %s",
msg().c_str(), CCTK_FullVarName(groupdata.firstvarindex + vi),
tl, string(required).c_str(),
string(groupdata.valid.at(tl).at(vi)).c_str());
}
void warn_if_invalid(const GHExt::GlobalData::ArrayGroupData &groupdata,
int vi, int tl, const valid_t &required,
const function<string()> &msg) {
const valid_t &have = groupdata.valid.at(tl).at(vi).get();
if (CCTK_BUILTIN_EXPECT((required & ~have).valid_any(), false))
CCTK_VWARN(CCTK_WARN_ALERT,
"%s: Grid array \"%s\" is invalid on time level %d; "
"required %s, found %s",
msg().c_str(), CCTK_FullVarName(groupdata.firstvarindex + vi),
tl, string(required).c_str(),
string(groupdata.valid.at(tl).at(vi)).c_str());
}
// Set (distrib=const) grid arrays to nan
void poison_invalid(const GHExt::GlobalData::ArrayGroupData &arraygroupdata,
int vi, int tl) {
DECLARE_CCTK_PARAMETERS;
if (!poison_undefined_values)
return;
const valid_t &valid = arraygroupdata.valid.at(tl).at(vi).get();
if (valid.valid_all())
return;
//TODO: poison needs to poison entire array, not just the first element
if (!valid.valid_int) {
CCTK_REAL *restrict const ptr =
const_cast<CCTK_REAL *>(&arraygroupdata.data.at(tl).at(vi));
*ptr = 0.0 / 0.0;
}
}
// Ensure grid arrays are not nan
void check_valid(const GHExt::GlobalData::ArrayGroupData &arraygroupdata,
int vi, int tl, const function<string()> &msg) {
DECLARE_CCTK_PARAMETERS;
if (!poison_undefined_values)
return;
const valid_t &valid = arraygroupdata.valid.at(tl).at(vi).get();
if (!valid.valid_any())
return;
// arrays have no boundary so we expect them to alway be valid
assert(valid.valid_outer && valid.valid_ghosts);
//TODO: nan checker needs to check every element of the arrays
// int ubnd = *arraygroupdata.info->ubnd;
atomic<size_t> nan_count{0};
if (valid.valid_int) {
const CCTK_REAL *restrict const ptr = &arraygroupdata.data.at(tl).at(vi);
// for (int i = 0; i < ubnd; i++) {
if (CCTK_BUILTIN_EXPECT(!CCTK_isfinite(*ptr), false)) {
++nan_count;
}
// }
}
if (CCTK_BUILTIN_EXPECT(nan_count > 0, false))
CCTK_VERROR("%s: Grid Array \"%s\" has %td nans on time level %d; "
"expected valid %s",
msg().c_str(),
CCTK_FullVarName(arraygroupdata.firstvarindex + vi),
size_t(nan_count), tl,
string(arraygroupdata.valid.at(tl).at(vi)).c_str());
auto &restrict scalargroupdata = *globaldata.scalargroupdata.at(gi);
for (int tl = 0; tl < int(scalargroupdata.data.size()); ++tl) {
const auto &restrict vars = scalargroupdata.data.at(tl);
for (int vi = 0; vi < scalargroupdata.numvars; ++vi) {
cctkGH->data[scalargroupdata.firstvarindex + vi][tl] =
const_cast<CCTK_REAL *>(&vars.at(vi));
} else if (group.grouptype == CCTK_SCALAR) {
auto &restrict scalargroupdata = *globaldata.scalargroupdata.at(gi);
for (int tl = 0; tl < int(scalargroupdata.data.size()); ++tl) {
const auto &restrict vars = scalargroupdata.data.at(tl);
for (int vi = 0; vi < scalargroupdata.numvars; ++vi) {
cctkGH->data[scalargroupdata.firstvarindex + vi][tl] =
const_cast<CCTK_REAL *>(&vars.at(vi));
}
} else { // CCTK_ARRAY
auto &restrict arraygroupdata = *globaldata.arraygroupdata.at(gi);
for (int tl = 0; tl < int(arraygroupdata.data.size()); ++tl) {
const auto &restrict vars = arraygroupdata.data.at(tl);
for (int vi = 0; vi < arraygroupdata.numvars; ++vi) {
cctkGH->data[arraygroupdata.firstvarindex + vi][tl] =
const_cast<CCTK_REAL *>(&vars.at(vi*arraygroupdata.array_size));
}
}
auto &restrict scalargroupdata = *globaldata.scalargroupdata.at(gi);
for (int tl = 0; tl < int(scalargroupdata.data.size()); ++tl) {
for (int vi = 0; vi < scalargroupdata.numvars; ++vi) {
cctkGH->data[scalargroupdata.firstvarindex + vi][tl] = nullptr;
} else if (group.grouptype == CCTK_SCALAR) {
auto &restrict scalargroupdata = *globaldata.scalargroupdata.at(gi);
for (int tl = 0; tl < int(scalargroupdata.data.size()); ++tl) {
for (int vi = 0; vi < scalargroupdata.numvars; ++vi) {
cctkGH->data[scalargroupdata.firstvarindex + vi][tl] = nullptr;
}
}
} else { // CCTK_ARRAY
auto &restrict arraygroupdata = *globaldata.arraygroupdata.at(gi);
for (int tl = 0; tl < int(arraygroupdata.data.size()); ++tl) {
for (int vi = 0; vi < arraygroupdata.numvars; ++vi) {
cctkGH->data[arraygroupdata.firstvarindex + vi][tl] = nullptr;
}
}
}
}
} else { // CCTK_ARRAY
auto &restrict globaldata = ghext->globaldata;
auto &restrict arraygroupdata = *globaldata.arraygroupdata.at(gi);
if (!arraygroupdata.do_checkpoint) {
// Invalidate all time levels
const int ntls = arraygroupdata.data.size();
for (int tl = 0; tl < ntls; ++tl) {
for (int vi = 0; vi < arraygroupdata.numvars; ++vi) {
// TODO: handle this more nicely
arraygroupdata.valid.at(tl).at(vi).set_int(false, [] {
return "InvalidateTimelevels (invalidate all non-checkpointed "
"variables)";
});
poison_invalid(arraygroupdata, vi, tl);
[&]() { return "CycleTimelevels"; });
} else { // CCTK_ARRAY
auto &restrict globaldata = ghext->globaldata;
auto &restrict arraygroupdata = *globaldata.arraygroupdata.at(gi);
const int ntls = arraygroupdata.data.size();
// Rotate time levels and invalidate current time level
if (ntls > 1) {
rotate(arraygroupdata.data.begin(), arraygroupdata.data.end() - 1,
arraygroupdata.data.end());
rotate(arraygroupdata.valid.begin(), arraygroupdata.valid.end() - 1,
arraygroupdata.valid.end());
for (int vi = 0; vi < arraygroupdata.numvars; ++vi) {
arraygroupdata.valid.at(0).at(vi).set_int(false, [] {
return "CycletimeLevels (invalidate current time level)";
});
poison_invalid(arraygroupdata, vi, 0);
}
}
for (int tl = 0; tl < ntls; ++tl)
for (int vi = 0; vi < arraygroupdata.numvars; ++vi)
check_valid(arraygroupdata, vi, tl,
ostringstream buf;
buf << "CallFunction iteration " << cctkGH->cctk_iteration << " "
<< attribute->where << ": " << attribute->thorn
<< "::" << attribute->routine << " checking input";
return buf.str();
});
} else { // CCTK_ARRAY
const auto &restrict arraygroupdata =
*ghext->globaldata.arraygroupdata.at(rd.gi);
const valid_t &need = rd.valid;
error_if_invalid(arraygroupdata, rd.vi, rd.tl, need, [&] {
ostringstream buf;
buf << "CallFunction iteration " << cctkGH->cctk_iteration << " "
<< attribute->where << ": " << attribute->thorn
<< "::" << attribute->routine << " checking input";
return buf.str();
});
check_valid(arraygroupdata, rd.vi, rd.tl, [&] {
} else { // CCTK_ARRAY
auto &restrict arraygroupdata =
*ghext->globaldata.arraygroupdata.at(wr.gi);
const valid_t &provided = wr.valid;
arraygroupdata.valid.at(wr.tl).at(wr.vi).set_and(
need | ~provided,
[iteration = cctkGH->cctk_iteration, where = attribute->where,
thorn = attribute->thorn, routine = attribute->routine] {
ostringstream buf;
buf << "CallFunction iteration " << iteration << " " << where
<< ": " << thorn << "::" << routine
<< ": Poison output variables that are not input variables";
return buf.str();
});
poison_invalid(arraygroupdata, wr.vi, wr.tl);
ostringstream buf;
buf << "CallFunction iteration " << cctkGH->cctk_iteration << " "
<< attribute->where << ": " << attribute->thorn
<< "::" << attribute->routine << " checking output";
return buf.str();
});
} else { // CCTK_ARRAY
auto &restrict arraygroupdata =
*ghext->globaldata.arraygroupdata.at(wr.gi);
const valid_t &provided = wr.valid;
arraygroupdata.valid.at(wr.tl).at(wr.vi).set_or(
provided,
[iteration = cctkGH->cctk_iteration, where = attribute->where,
thorn = attribute->thorn, routine = attribute->routine] {
ostringstream buf;
buf << "CallFunction iteration " << iteration << " " << where
<< ": " << thorn << "::" << routine
<< ": Mark output variables as valid";
return buf.str();
});
check_valid(arraygroupdata, wr.vi, wr.tl, [&]() {
ostringstream buf;
buf << "CallFunction iteration " << cctkGH->cctk_iteration << " "
<< attribute->where << ": " << attribute->thorn
<< "::" << attribute->routine << " checking output";
return buf.str();
});
} else { // CCTK_ARRAY
auto &restrict arraygroupdata =
*ghext->globaldata.arraygroupdata.at(inv.gi);
const valid_t &provided = inv.valid;
arraygroupdata.valid.at(inv.tl).at(inv.vi).set_and(
~provided,
[iteration = cctkGH->cctk_iteration, where = attribute->where,
thorn = attribute->thorn, routine = attribute->routine] {
ostringstream buf;
buf << "CallFunction iteration " << iteration << " " << where
<< ": " << thorn << "::" << routine
<< ": Mark invalid variables as invalid";
return buf.str();
});
check_valid(arraygroupdata, inv.vi, inv.tl, [&]() {
void error_if_invalid(const GHExt::GlobalData::ArrayGroupData &groupdata,
int vi, int tl, const valid_t &required,
const function<string()> &msg);
void warn_if_invalid(const GHExt::GlobalData::ArrayGroupData &groupdata,
int vi, int tl, const valid_t &required,
const function<string()> &msg);
void poison_invalid(const GHExt::GlobalData::ArrayGroupData &groupdata, int vi,
int tl);
void check_valid(const GHExt::GlobalData::ArrayGroupData &groupdata, int vi,
int tl, const function<string()> &msg);