Skip to content

Commit be013ae

Browse files
committed
detect stochastic mutationEffect() callbacks to disable DEBUG crosschecks
1 parent 21dc221 commit be013ae

7 files changed

Lines changed: 52 additions & 17 deletions

File tree

core/individual.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7398,6 +7398,9 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual
73987398
SLiMEidosBlockType old_executing_block_type = species->community_.executing_block_type_;
73997399
species->community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMutationEffectCallback;
74007400

7401+
// In DEBUG we will watch for RNG usage and disable trait value crosschecks if stochastic callbacks are involved
7402+
EIDOS_RNG_PING_RESET();
7403+
74017404
// Calculate the specified phenotypes for the individuals; this loops through all chromosomes, handling
74027405
// ploidy and callbacks as needed. It is very nice to have the top-level loop be over the chromosomes,
74037406
// so that each one can do a single timing for mutrun experiments.
@@ -7609,7 +7612,10 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual
76097612

76107613
#if DEBUG
76117614
// Do a check of all computed results, against the same values computed by brute force.
7612-
// FIXME MULTITRAIT: Since this is incorrect with stochastic callbacks, it should be turned into a warning before ship
7615+
// If we noticed RNG use during trait calculations, we turn off these crosschecks.
7616+
if (EIDOS_RNG_PING_IS_SET())
7617+
gSLiM_disable_trait_crosschecks = true;
7618+
76137619
if (gSLiM_disable_trait_crosschecks)
76147620
return;
76157621

@@ -7850,6 +7856,9 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s
78507856
SLiMEidosBlockType old_executing_block_type = species->community_.executing_block_type_;
78517857
species->community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMutationEffectCallback;
78527858

7859+
// In DEBUG we will watch for RNG usage and disable trait value crosschecks if stochastic callbacks are involved
7860+
EIDOS_RNG_PING_RESET();
7861+
78537862
// Calculate the specified phenotypes for the individuals; this loops through all chromosomes, handling
78547863
// ploidy and callbacks as needed. It is very nice to have the top-level loop be over the chromosomes,
78557864
// so that each one can do a single timing for mutrun experiments.
@@ -8243,7 +8252,10 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s
82438252

82448253
#if DEBUG
82458254
// Do a check of all computed results, against the same values computed by brute force.
8246-
// FIXME MULTITRAIT: Since this is incorrect with stochastic callbacks, it should be turned into a warning before ship
8255+
// If we noticed RNG use during trait calculations, we turn off these crosschecks.
8256+
if (EIDOS_RNG_PING_IS_SET())
8257+
gSLiM_disable_trait_crosschecks = true;
8258+
82478259
if (gSLiM_disable_trait_crosschecks)
82488260
return;
82498261

core/slim_globals.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ void AccumulateMemoryUsageIntoTotal_Community(SLiMMemoryUsage_Community &p_usage
628628
#pragma mark Debugging support
629629
#pragma mark -
630630

631-
bool gSLiM_disable_trait_crosschecks = false;;
631+
bool gSLiM_disable_trait_crosschecks = false;
632632

633633

634634
#pragma mark -

core/slim_globals.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,10 @@ void AccumulateMemoryUsageIntoTotal_Community(SLiMMemoryUsage_Community &p_usage
549549
extern int64_t SLiM_verbosity_level;
550550

551551
// This flag can be set to true to disable trait value crosschecks; this is necessary in some cases if stochastic
552-
// mutationEffect() callbacks are in effect, or if phenotype values are being set directly by the script.
552+
// mutationEffect() callbacks are in effect, or if phenotype values are being set directly by the script. This
553+
// flag will stay on if set, except that it is reset to false in _SLiMTestCleanup(). It is automatically set
554+
// to true if stochastic mutationEffect() callbacks are detected, but that does not cover all possible cases, so
555+
// false positives from the trait crosschecks may be observed in DEBUG builds, which is why this flag exists.
553556
extern bool gSLiM_disable_trait_crosschecks;
554557

555558

core/slim_test.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ static void _SLiMTestCleanup(Community *community)
5757
std::cerr << "WARNING (SLiMAssertScriptSuccess): gEidos_DictionaryNonRetainReleaseReferenceCounter == " << gEidos_DictionaryNonRetainReleaseReferenceCounter << " at end of test!" << std::endl;
5858

5959
gEidos_DictionaryNonRetainReleaseReferenceCounter = 0;
60+
61+
gSLiM_disable_trait_crosschecks = false;
6062
}
6163

6264
// Instantiates and runs the script, and prints an error if the result does not match expectations

core/slim_test_genetics.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,8 +2857,6 @@ reproduction() { }
28572857

28582858
// this script tests invalidation of trait values when a non-constant mutationEffect() callback is active
28592859
// - trait values in all individuals should be invalidated at the start of each demand phase
2860-
gSLiM_disable_trait_crosschecks = true; // crosschecks don't like the stochastic callback here!
2861-
28622860
#pragma mark multitrait_INVALIDATE_8
28632861
std::string multitrait_INVALIDATE_8 =
28642862
R"V0G0N(
@@ -2905,8 +2903,6 @@ mutationEffect(m1) { return runif(1, 0.01, 0.99); }
29052903

29062904
SLiMAssertScriptSuccess(multitrait_INVALIDATE_8);
29072905

2908-
gSLiM_disable_trait_crosschecks = false; // resume crosschecks
2909-
29102906

29112907
// this script tests invalidation of trait values when a mutationEffect() callback comes in and out
29122908
// of scope, is registered/deregistered, is rescheduled, or is activated/deactivated

eidos/eidos_rng.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ Eidos_RNG_State gEidos_RNG_SINGLE;
8484
std::vector<Eidos_RNG_State *> gEidos_RNG_PERTHREAD;
8585
#endif
8686

87+
#if DEBUG
88+
bool gEidos_RNG_usageFlag = false; // see the header for comments
89+
#endif
90+
8791

8892
static unsigned long int _Eidos_GenerateRNGSeed(void)
8993
{

eidos/eidos_rng.h

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,25 @@ extern Eidos_RNG_State gEidos_RNG_SINGLE;
9999
extern std::vector<Eidos_RNG_State *> gEidos_RNG_PERTHREAD;
100100
#endif
101101

102-
// Calls to the GSL should use these macros to get the RNG state they need, whether single- or multi-threaded.
102+
#if DEBUG
103+
// This global flag is a bit of a hack. It's a way for Eidos client code to detect when the random number
104+
// generator has been used by the Eidos interpreter, which might be of interest. SLiM uses this to disable
105+
// some checks that are invalid if the Eidos code executed is stochastic. This is NOT thread-safe, in the
106+
// sense that if different threads are try to use this facility simultaneously it will not work. It is
107+
// also just hacky enough that it probably shouldn't be used in production code, so it is DEBUG only.
108+
// The flag is set whenever one of the RNG accessors below is called.
109+
extern bool gEidos_RNG_usageFlag;
110+
111+
inline __attribute__((always_inline)) void EIDOS_RNG_PING_RESET(void) { gEidos_RNG_usageFlag = false; }
112+
inline __attribute__((always_inline)) void EIDOS_RNG_PING(void) { gEidos_RNG_usageFlag = true; }
113+
inline __attribute__((always_inline)) bool EIDOS_RNG_PING_IS_SET(void) { return gEidos_RNG_usageFlag; }
114+
#else
115+
inline __attribute__((always_inline)) void EIDOS_RNG_PING_RESET(void) { }
116+
inline __attribute__((always_inline)) void EIDOS_RNG_PING(void) { }
117+
inline __attribute__((always_inline)) bool EIDOS_RNG_PING_IS_SET(void) { return false; }
118+
#endif
119+
120+
// Calls to the GSL should use these functions to get the RNG state they need, whether single- or multi-threaded.
103121
// BCH 11/5/2022: The thread number must now be supplied. It will be zero when single-threaded, and so is
104122
// ignored. Since this is now a bit more heavyweight, the RNG for a thread should be obtained outside of any
105123
// core loops. The most important thing is that when there is a parallel region, the RNG is obtained INSIDE
@@ -111,15 +129,15 @@ extern std::vector<Eidos_RNG_State *> gEidos_RNG_PERTHREAD;
111129
// Eidos_RNG_State *rng_state = EIDOS_STATE_RNG(omp_get_thread_num());
112130
//
113131
#ifndef _OPENMP
114-
#define EIDOS_GSL_RNG(threadnum) (&gEidos_RNG_SINGLE.gsl_rng_)
115-
#define EIDOS_32BIT_RNG(threadnum) (gEidos_RNG_SINGLE.pcg32_rng_)
116-
#define EIDOS_64BIT_RNG(threadnum) (gEidos_RNG_SINGLE.pcg64_rng_)
117-
#define EIDOS_STATE_RNG(threadnum) (&gEidos_RNG_SINGLE)
132+
inline __attribute__((always_inline)) gsl_rng *EIDOS_GSL_RNG(int threadnum) { EIDOS_RNG_PING(); return &gEidos_RNG_SINGLE.gsl_rng_; }
133+
inline __attribute__((always_inline)) EidosRNG_32_bit &EIDOS_32BIT_RNG(int threadnum) { EIDOS_RNG_PING(); return gEidos_RNG_SINGLE.pcg32_rng_; }
134+
inline __attribute__((always_inline)) EidosRNG_64_bit &EIDOS_64BIT_RNG(int threadnum) { EIDOS_RNG_PING(); return gEidos_RNG_SINGLE.pcg64_rng_; }
135+
inline __attribute__((always_inline)) Eidos_RNG_State *EIDOS_STATE_RNG(int threadnum) { EIDOS_RNG_PING(); return &gEidos_RNG_SINGLE; }
118136
#else
119-
#define EIDOS_GSL_RNG(threadnum) (&gEidos_RNG_PERTHREAD[threadnum]->gsl_rng_)
120-
#define EIDOS_32BIT_RNG(threadnum) (gEidos_RNG_PERTHREAD[threadnum]->pcg32_rng_)
121-
#define EIDOS_64BIT_RNG(threadnum) (gEidos_RNG_PERTHREAD[threadnum]->pcg64_rng_)
122-
#define EIDOS_STATE_RNG(threadnum) (gEidos_RNG_PERTHREAD[threadnum])
137+
inline __attribute__((always_inline)) gsl_rng *EIDOS_GSL_RNG(int threadnum) { EIDOS_RNG_PING(); return &gEidos_RNG_PERTHREAD[threadnum]->gsl_rng_; }
138+
inline __attribute__((always_inline)) EidosRNG_32_bit &EIDOS_32BIT_RNG(int threadnum) { EIDOS_RNG_PING(); return gEidos_RNG_PERTHREAD[threadnum]->pcg32_rng_; }
139+
inline __attribute__((always_inline)) EidosRNG_64_bit &EIDOS_64BIT_RNG(int threadnum) { EIDOS_RNG_PING(); return gEidos_RNG_PERTHREAD[threadnum]->pcg64_rng_; }
140+
inline __attribute__((always_inline)) Eidos_RNG_State *EIDOS_STATE_RNG(int threadnum) { EIDOS_RNG_PING(); return &gEidos_RNG_PERTHREAD[threadnum]; }
123141
#endif
124142

125143
#if DEBUG

0 commit comments

Comments
 (0)