Skip to content

Commit 75d7594

Browse files
committed
add a bunch of multitrait debug test code
1 parent 9e6d38d commit 75d7594

File tree

9 files changed

+868
-84
lines changed

9 files changed

+868
-84
lines changed

core/individual.cpp

Lines changed: 677 additions & 72 deletions
Large diffs are not rendered by default.

core/individual.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,13 @@ class Individual : public EidosDictionaryUnretained
417417
template <const bool f_additiveTrait, const bool f_callbacks, const bool f_singlecallback>
418418
void _IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector<SLiMEidosBlock*> &p_mutationEffect_callbacks);
419419

420+
// Debugging checkback for phenotype calculation; this is very slow, and does not use the non-neutral cache
421+
slim_effect_t _CheckPhenotypeForTrait(slim_trait_index_t trait_index);
422+
423+
void _Check_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector<SLiMEidosBlock*> &p_mutationEffect_callbacks);
424+
void _Check_IncorporateEffects_Hemizygous(Species *species, Haplosome *haplosome, Trait *trait, std::vector<SLiMEidosBlock*> &p_mutationEffect_callbacks);
425+
void _Check_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector<SLiMEidosBlock*> &p_mutationEffect_callbacks);
426+
420427
// for Subpopulation::ExecuteMethod_takeMigrants()
421428
friend Subpopulation;
422429
};

core/slim_globals.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,30 @@ std::string StringForSLiMCycleStage(SLiMCycleStage p_stage)
653653
}
654654

655655
// stream output for enumerations
656+
std::string StringForTraitType(TraitType p_trait_type)
657+
{
658+
switch (p_trait_type)
659+
{
660+
case TraitType::kAdditive: return gStr_additive;
661+
case TraitType::kMultiplicative: return gStr_multiplicative;
662+
}
663+
EIDOS_TERMINATION << "ERROR (StringForTraitType): (internal error) unexpected p_trait_type value." << EidosTerminate();
664+
}
665+
666+
TraitType TraitTypeForString(std::string type)
667+
{
668+
if (type == gStr_additive) return TraitType::kAdditive;
669+
else if (type == gStr_multiplicative) return TraitType::kMultiplicative;
670+
else
671+
EIDOS_TERMINATION << "ERROR (TraitTypeForString): unrecognized triat type '" << type << "'." << EidosTerminate();
672+
}
673+
674+
std::ostream& operator<<(std::ostream& p_out, TraitType p_trait_type)
675+
{
676+
p_out << StringForTraitType(p_trait_type);
677+
return p_out;
678+
}
679+
656680
std::string StringForChromosomeType(ChromosomeType p_chromosome_type)
657681
{
658682
switch (p_chromosome_type)
@@ -1629,6 +1653,8 @@ const std::string &gStr_willAutolog = EidosRegisteredString("willAutolog", gID_w
16291653
const std::string &gStr_context = EidosRegisteredString("context", gID_context);
16301654

16311655
// mostly other fixed strings
1656+
const std::string gStr_additive = "additive"; // these trait type strings are not registered, no need
1657+
const std::string gStr_multiplicative = "multiplicative";
16321658
const std::string gStr_A = "A"; // these nucleotide strings are not registered, no need
16331659
const std::string gStr_C = "C";
16341660
const std::string gStr_G = "G";

core/slim_globals.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ void AccumulateMemoryUsageIntoTotal_Community(SLiMMemoryUsage_Community &p_usage
492492
#define DEBUG_BLOCK_REG_DEREG 0 // turn on to get logging about script block registration/deregistration
493493
#define DEBUG_SHUFFLE_BUFFER 1 // debug memory overruns with the shuffle buffer
494494
#define DEBUG_TICK_RANGES 0 // debug tick range parsing and evaluation
495+
#define DEBUG_TRAIT_DEMAND 0 // enable debugging logs about the trait "demand" evalutation process
495496
#define DEBUG_LESS_INTENSIVE 0 // decrease the frequency of some very intensive DEBUG checks
496497

497498

@@ -581,6 +582,10 @@ enum class TraitType : uint8_t {
581582
kAdditive
582583
};
583584

585+
std::string StringForTraitType(TraitType p_trait_type);
586+
TraitType TraitTypeForString(std::string type); // raises if no match
587+
std::ostream& operator<<(std::ostream& p_out, TraitType p_trait_type);
588+
584589
// This enumeration represents the type of a chromosome. Note that the sex of an individual cannot always be inferred
585590
// from chromosomal state, and the user is allowed to play games with null haplosomes; the chromosomes follow the sex
586591
// of the individual, the sex of the individual does not follow the chromosomes. See the initializeChromosome() doc.
@@ -1213,6 +1218,8 @@ extern const std::string &gStr_setSuppliedValue;
12131218
extern const std::string &gStr_willAutolog;
12141219
extern const std::string &gStr_context;
12151220

1221+
extern const std::string gStr_additive; // these trait type strings are not registered, no need
1222+
extern const std::string gStr_multiplicative;
12161223
extern const std::string gStr_A; // these nucleotide strings are not registered, no need
12171224
extern const std::string gStr_C;
12181225
extern const std::string gStr_G;

core/slim_test_genetics.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,120 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); }
15931593

15941594
SLiMAssertScriptSuccess(test_zygosity4);
15951595

1596+
1597+
// Complex multi-trait, multi-chrom models that will (hopefully) exercise all the different code paths for
1598+
// phenotype evaluation -- callbacks, different DES combinations, neutral and non-neutral, etc.
1599+
// The intention here is that these models don't test themselves; they are stochastic and there is no
1600+
// expectation regarding their outcome. Instead, _CheckPhenotypeForTrait() cross-checks them under DEBUG.
1601+
1602+
std::string complex_multi_1 = // this is an abbreviated version of test script complex_multi_test_1.slim
1603+
R"V0G0N(
1604+
initialize() {
1605+
defineConstant("I1", 5.0);
1606+
defineConstant("I2", -5.0);
1607+
defineConstant("OPT1", 10.0);
1608+
defineConstant("OPT2", 10.0);
1609+
defineConstant("SD1", 2.0);
1610+
defineConstant("SD2", 2.0);
1611+
1612+
initializeSex();
1613+
1614+
// multiplicative traits
1615+
popgen1T = initializeTrait("popgen1T", "mul", 1.01, 1.0, 0.01, directFitnessEffect=T); // will have a mix of dominance
1616+
popgen2T = initializeTrait("popgen2T", "mul", 1.01, 1.0, 0.01, directFitnessEffect=T); // will be independent dominance
1617+
n1T = initializeTrait("n1T", "mul", NULL, NULL, NULL, directFitnessEffect=T); // neutral with direct effect
1618+
n2T = initializeTrait("n2T", "mul", NULL, NULL, NULL, directFitnessEffect=F); // neutral with no direct effect
1619+
1620+
// additive traits
1621+
quant1T = initializeTrait("quant1T", "add", I1, 0.0, 0.01, directFitnessEffect=F); // will have a mix of dominance
1622+
quant2T = initializeTrait("quant2T", "add", I2, 0.0, 0.01, directFitnessEffect=F); // will be independent dominance
1623+
n3T = initializeTrait("n3T", "add", NULL, NULL, NULL, directFitnessEffect=F); // non-neutral with no direct effect
1624+
1625+
// quant1T / quant2T will be demanded in script; popgen1T / popgen2T / n1T will be demanded because they have direct effects
1626+
// calculation of popgen2T and quant2T should be extremely efficient since they are independent dominance
1627+
// calculation of n1T should be omitted entirely; SLiM should detect that it is neutral, and not even set phenotype values
1628+
// n2T and n3T should not be demanded, and should thus never be calculated by SLiM, which we can check in script
1629+
1630+
// mutation types
1631+
initializeMutationType("m1", 0.4, "f", 0.0); // neutral for all traits
1632+
1633+
initializeMutationType("m2", 0.4, "e", 0.001); // beneficial for the popgen traits
1634+
m2.setEffectDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits
1635+
m2.setEffectDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits
1636+
1637+
initializeMutationType("m3", 0.4, "g", -0.001, 1.0); // deleterious for the popgen traits
1638+
m3.setEffectDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits
1639+
m3.setEffectDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits
1640+
1641+
c(m2,m3).setEffectDistributionForTrait(n3T, "n", -5.0, 0.5); // very biased and wide DES for n3T
1642+
1643+
// set up independent dominance for popgen2T and quant2T; note that setting this for m1 should be unnecessary (it is neutral)
1644+
c(m1,m2,m3).setDefaultDominanceForTrait(c(popgen2T, quant2T), NAN);
1645+
1646+
// prevent converting to substitutions for m2 and m3; once shifting the baseline offset is supported, this will not be needed
1647+
c(m2,m3).convertToSubstitution = F;
1648+
1649+
initializeGenomicElementType("g1", m1, 1.0); // neutral
1650+
initializeGenomicElementType("g2", 1:3, c(2, 1, 1)); // mixture
1651+
1652+
ids = 1:5;
1653+
symbols = c(1, 2, "X", "Y", "MT");
1654+
lengths = rdunif(5, 1e7, 2e7);
1655+
types = c("A", "A", "X", "Y", "H");
1656+
names = c("A1", "A2", "X", "Y", "MT");
1657+
1658+
for (id in ids, symbol in symbols, length in lengths, type in types, name in names)
1659+
{
1660+
initializeChromosome(id, length, type, symbol, name);
1661+
initializeMutationRate(1e-7);
1662+
initializeRecombinationRate(1e-8);
1663+
1664+
if (id == 1)
1665+
initializeGenomicElement(g1); // autosome 1 is pure neutral, using only m1
1666+
else
1667+
initializeGenomicElement(g2); // autosome 2 is a mix, using m1 / m2 / m3
1668+
}
1669+
}
1670+
1671+
// set random dominance effects for the popgen1T and quant1T traits
1672+
// other effects are generated as specified by the mutation type DES
1673+
mutation(m2) {
1674+
mut.popgen1TDominance = runif(1);
1675+
mut.quant1TDominance = runif(1);
1676+
return T;
1677+
}
1678+
mutation(m3) {
1679+
mut.popgen1TDominance = runif(1);
1680+
mut.quant1TDominance = runif(1);
1681+
return T;
1682+
}
1683+
1684+
1 late() {
1685+
sim.addSubpop("p1", 20);
1686+
}
1687+
1688+
1: late() {
1689+
inds = sim.subpopulations.individuals;
1690+
sim.demandPhenotype(NULL, c(sim.quant1T, sim.quant2T));
1691+
fitnessEffect_q1 = dnorm(inds.quant1T, OPT1, SD1) / dnorm(0.0, 0.0, SD1);
1692+
fitnessEffect_q2 = dnorm(inds.quant2T, OPT2, SD2) / dnorm(0.0, 0.0, SD2);
1693+
inds.fitnessScaling = fitnessEffect_q1 * fitnessEffect_q2;
1694+
}
1695+
1696+
2: first() {
1697+
inds = sim.subpopulations.individuals;
1698+
1699+
// check that traits that do not require calculation remain uncalculated
1700+
//if (!all(isNAN(inds.n1T))) stop("n1T was calculated unnecessarily"); // the smarts for this are not yet implemented!
1701+
if (!all(isNAN(inds.n2T))) stop("n2T was calculated unnecessarily");
1702+
if (!all(isNAN(inds.n3T))) stop("n3T was calculated unnecessarily");
1703+
}
1704+
1705+
50 late() { }
1706+
)V0G0N";
1707+
1708+
SLiMAssertScriptSuccess(complex_multi_1);
1709+
15961710
std::cout << "_RunMultitraitTests() done" << std::endl;
15971711
}
15981712

core/species.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ void Species::CheckOptimizationFlags(void)
392392
slim_trait_index_t trait_index = trait->Index();
393393
MutationTraitInfo &trait_info = mut_trait_info[trait_index];
394394

395-
if (trait_info.effect_size_ != 0.0)
395+
if (trait_info.effect_size_ != (slim_effect_t)0.0)
396396
{
397397
// this mutation is non-neutral for this trait
398398
if (trait->trait_all_neutral_mutations_ != false)
@@ -427,7 +427,7 @@ void Species::_NoteNonNeutralMutation(const Mutation *p_mut)
427427
slim_trait_index_t trait_index = trait->Index();
428428
MutationTraitInfo &trait_info = mut_trait_info[trait_index];
429429

430-
if (trait_info.effect_size_ != 0.0)
430+
if (trait_info.effect_size_ != (slim_effect_t)0.0)
431431
{
432432
// this mutation is non-neutral for this trait
433433
trait->trait_all_neutral_mutations_ = false;

core/species_eidos.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1678,9 +1678,9 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string
16781678
std::string type_string = type_value->StringAtIndex_NOCAST(0, nullptr);
16791679
TraitType type;
16801680

1681-
if ((type_string == "multiplicative") || (type_string == "mul"))
1681+
if ((type_string == gStr_multiplicative) || (type_string == "mul"))
16821682
type = TraitType::kMultiplicative;
1683-
else if ((type_string == "additive") || (type_string == "add"))
1683+
else if ((type_string == gStr_additive) || (type_string == "add"))
16841684
type = TraitType::kAdditive;
16851685
else
16861686
EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires type to be either 'multiplicative' (or 'mul'), or 'additive' ('add')." << EidosTerminate();
@@ -3117,13 +3117,14 @@ EidosValue_SP Species::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_metho
31173117
eidos_logical_t forceRecalc = forceRecalc_value->LogicalAtIndex_NOCAST(0, nullptr);
31183118

31193119
// subpops
3120-
std::vector<Subpopulation*> subpops_to_demand;
3120+
THREAD_SAFETY_IN_ACTIVE_PARALLEL("Species::ExecuteMethod_demandPhenotype(): usage of statics");
3121+
3122+
static std::vector<Subpopulation*> subpops_to_demand; // using and clearing a static prevents allocation thrash; should be safe from re-entry
3123+
subpops_to_demand.resize(0);
31213124

31223125
if (subpops_value->Type() == EidosValueType::kValueNULL)
31233126
{
31243127
// demand the specified phenotypes across all subpopulations
3125-
subpops_to_demand.resize(population_.subpops_.size());
3126-
31273128
for (const std::pair<const slim_objectid_t,Subpopulation*> &subpop_pair : population_.subpops_)
31283129
subpops_to_demand.push_back(subpop_pair.second);
31293130
}
@@ -3134,17 +3135,25 @@ EidosValue_SP Species::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_metho
31343135

31353136
if (requested_subpop_count)
31363137
{
3137-
subpops_to_demand.resize(requested_subpop_count);
3138-
31393138
for (int requested_subpop_index = 0; requested_subpop_index < requested_subpop_count; ++requested_subpop_index)
3140-
subpops_to_demand.emplace_back(SLiM_ExtractSubpopulationFromEidosValue_io(subpops_value, requested_subpop_index, &community_, this, "demandPhenotype()")); // SPECIES CONSISTENCY CHECK
3139+
subpops_to_demand.push_back(SLiM_ExtractSubpopulationFromEidosValue_io(subpops_value, requested_subpop_index, &community_, this, "demandPhenotype()")); // SPECIES CONSISTENCY CHECK
31413140

31423141
// unique subpops_to_demand to avoid duplicated work
31433142
std::sort(subpops_to_demand.begin(), subpops_to_demand.end());
31443143
subpops_to_demand.resize(static_cast<size_t>(std::distance(subpops_to_demand.begin(), std::unique(subpops_to_demand.begin(), subpops_to_demand.end()))));
31453144
}
31463145
}
31473146

3147+
#if DEBUG_TRAIT_DEMAND
3148+
std::cout << "# " << community_.Tick() << " --- demandPhenotype(): for traits {";
3149+
for (slim_trait_index_t trait_index : trait_indices)
3150+
std::cout << " " << Traits()[trait_index]->Name();
3151+
std::cout << " } in subpops {";
3152+
for (Subpopulation *subpop : subpops_to_demand)
3153+
std::cout << " p" << subpop->subpopulation_id_;
3154+
std::cout << " }, forceRecalc == " << (forceRecalc ? "T" : "F") << std::endl;
3155+
#endif
3156+
31483157
// validate non-neutral caches and independent-dominance precalculated values
31493158
// FIXME MULTITRAIT: VALIDATE NON-NEUTRAL CACHES HERE
31503159

core/subpopulation.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,9 @@ void Subpopulation::UpdateFitness(std::vector<SLiMEidosBlock*> &p_subpop_mutatio
13871387
{
13881388
// we know this subpopulation has effectively constant fitness; we therefore don't express demand for
13891389
// any traits, which means trait may keep NAN values even if the traits have a direct fitness effect
1390+
#if DEBUG_TRAIT_DEMAND
1391+
std::cout << "# " << community_.Tick() << " --- UpdateFitness() determined constant fitness of " << constant_fitness_value << std::endl;
1392+
#endif
13901393

13911394
if (model_type_ == SLiMModelType::kModelTypeWF)
13921395
{
@@ -1425,11 +1428,24 @@ void Subpopulation::UpdateFitness(std::vector<SLiMEidosBlock*> &p_subpop_mutatio
14251428
// demand phenotypes for all the relevant traits
14261429
if (p_direct_effect_trait_indices.size())
14271430
{
1431+
#if DEBUG_TRAIT_DEMAND
1432+
std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding traits {";
1433+
for (slim_trait_index_t trait_index : p_direct_effect_trait_indices)
1434+
std::cout << " " << species_.Traits()[trait_index]->Name();
1435+
std::cout << " } in subpop p" << subpopulation_id_ << ", forceRecalc == " << (p_force_trait_recalculation ? "T" : "F") << std::endl;
1436+
#endif
1437+
14281438
if (p_force_trait_recalculation)
14291439
Individual_Class::DemandPhenotype_SUBPOP<true>(&species_, this, p_direct_effect_trait_indices, p_subpop_mutationEffect_callbacks);
14301440
else
14311441
Individual_Class::DemandPhenotype_SUBPOP<false>(&species_, this, p_direct_effect_trait_indices, p_subpop_mutationEffect_callbacks);
14321442
}
1443+
else
1444+
{
1445+
#if DEBUG_TRAIT_DEMAND
1446+
std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding NO traits in subpop p" << subpopulation_id_ << std::endl;
1447+
#endif
1448+
}
14331449

14341450
// then loop over individuals and pull together the relevant phenotype values, fitnessEffect() callbacks,
14351451
// subpopulation fitnessScaling, and individual fitnessScaling to produce final individual fitness values;

core/trait.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id)
113113
{
114114
if (!static_type_string_multiplicative)
115115
{
116-
static_type_string_multiplicative = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("multiplicative"));
117-
static_type_string_additive = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("additive"));
116+
static_type_string_multiplicative = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_multiplicative));
117+
static_type_string_additive = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_additive));
118118
}
119119
}
120120

0 commit comments

Comments
 (0)