From a494c45b7e3f330959718f018fbe38be01377cb8 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Tue, 1 Jul 2025 18:35:13 +0200 Subject: [PATCH 001/107] add Trait class, initializeTrait(), and related stuff --- QtSLiM/help/SLiMHelpClasses.html | 30 +++ QtSLiM/help/SLiMHelpFunctions.html | 8 + SLiM.xcodeproj/project.pbxproj | 14 ++ SLiMgui/SLiMHelpClasses.rtf | 167 ++++++++++++++++- SLiMgui/SLiMHelpFunctions.rtf | 88 +++++++++ VERSIONS | 17 ++ core/community_eidos.cpp | 15 ++ core/individual.cpp | 39 ++++ core/individual.h | 4 + core/slim_globals.cpp | 12 ++ core/slim_globals.h | 29 +++ core/species.cpp | 60 ++++++ core/species.h | 33 +++- core/species_eidos.cpp | 196 +++++++++++++++++++- core/trait.cpp | 254 ++++++++++++++++++++++++++ core/trait.h | 138 ++++++++++++++ eidos/eidos_class_Object.cpp | 19 +- eidos/eidos_class_Object.h | 3 + eidos/eidos_globals.h | 2 +- eidos/eidos_test_functions_vector.cpp | 2 +- eidos/eidos_value.cpp | 42 ++++- 21 files changed, 1152 insertions(+), 20 deletions(-) create mode 100644 core/trait.cpp create mode 100644 core/trait.h diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 10d3db5f..ba83b039 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -184,6 +184,8 @@

All of the Species objects defined in the simulation (in species declaration order).

allSubpopulations => (object<Subpopulation>)

All of the Subpopulation objects defined in the simulation.

+

allTraits => (object<Trait>)

+

All of the Trait objects defined in the simulation (in species declaration order, primarily, and in order of their index within a species, secondarily).

cycleStage => (string$)

The current cycle stage, as a string.  The values of this property essentially mirror the cycle stages of WF and nonWF models.  Common values include "first" (during execution of first() events), "early" (during execution of early() events), "reproduction" (during offspring generation), "fitness" (during fitness evaluation), "survival" (while applying selection and mortality in nonWF models), and "late" (during execution of late() events).

Other possible values include "begin" (during internal setup before each cycle), "tally" (while tallying mutation reference counts and removing fixed mutations), "swap" (while swapping the offspring generation into the parental generation in WF models), "end" (during internal bookkeeping after each cycle), and "console" (during the in-between-ticks state in which commands in SLiMgui’s Eidos console are executed).  It would probably be a good idea not to use this latter set of values; they are probably not user-visible during ordinary model execution anyway.

@@ -940,6 +942,8 @@

A vector of Substitution objects, representing all mutations that have been fixed in this species.

tag <–> (integer$)

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.  See also the getValue() and setValue() methods (provided by the Dictionary class; see the Eidos manual), for another way of attaching state to the simulation.

+

traits => (object<Trait>)

+

The Trait objects defined in the species.  These are guaranteed to be in sorted order by their index (i.e., by the order in which they were defined).

5.16.2  Species methods

– (object<Dictionary>$)addPatternForClone(iso<Chromosome>$ chromosome, No<Dictionary>$ pattern, object<Individual>$ parent, [Ns$ sex = NULL])

Adds an inheritance dictionary for the specified chromosome to the pattern dictionary pattern, representing producing a clone of parent, with sex optionally specified by sex.  The parameter chromosome can provide a chromosome id (an integer), a chromosome symbol (a string), or a Chromosome object.  The resulting pattern dictionary is intended for use with the Subpopulation method addMultiRecombinant(); see that method for background on the use of pattern dictionaries.

@@ -1056,6 +1060,10 @@

This method is shorthand for getting the mutations property of the subpopulation, and then using operator [] to select only mutations with the desired properties; besides being much simpler than the equivalent Eidos code, it is also much faster.  Note that if you only need to select on mutation type, the mutationsOfType() method will be even faster.

– (object<Substitution>)substitutionsOfType(io<MutationType>$ mutType)

Returns an object vector of all the substitutions that are of the type specified by mutType, out of all of the substitutions that are currently present in the species.  This method is provided for speed; it is much faster than the corresponding Eidos code.  See also mutationsOfType().

+

– (object<Trait>)traitsWithIndices(integer indices)

+

Returns a vector of Trait objects corresponding to the trait indices supplied in indices, in the same order.  If any index in indices does not correspond to a trait in the target species, an error will be raised.  See also traitsWithNames().

+

– (object<Trait>)traitsWithNames(string names)

+

Returns a vector of Trait objects corresponding to the trait names supplied in names, in the same order.  If any name in names does not correspond to a trait in the target species, an error will be raised.  See also traitsWithIndices().

– (logical$)treeSeqCoalesced(void)

Returns the coalescence state for the recorded tree sequence at the last simplification.  The returned value is a logical singleton flag, T to indicate that full coalescence was observed at the last tree-sequence simplification (meaning that there is a single ancestral individual that roots all ancestry trees at all sites along the chromosome – although not necessarily the same ancestor at all sites), or F if full coalescence was not observed.  For simple models, reaching coalescence may indicate that the model has reached an equilibrium state, but this may not be true in models that modify the dynamics of the model during execution by changing migration rates, introducing new mutations programmatically, dictating non-random mating, etc., so be careful not to attach more meaning to coalescence than it is due; some models may require burn-in beyond coalescence to reach equilibrium, or may not have an equilibrium state at all.  Also note that some actions by a model, such as adding a new subpopulation, may cause the coalescence state to revert from T back to F (at the next simplification), so a return value of T may not necessarily mean that the model is coalesced at the present moment – only that it was coalesced at the last simplification.

This method may only be called if tree sequence recording has been turned on with initializeTreeSeq(); in addition, checkCoalescence=T must have been supplied to initializeTreeSeq(), so that the necessary work is done during each tree-sequence simplification.  Since this method does not perform coalescence checking itself, but instead simply returns the coalescence state observed at the last simplification, it may be desirable to call treeSeqSimplify() immediately before treeSeqCoalesced() to obtain up-to-date information.  However, the speed penalty of doing this in every tick would be large, and most models do not need this level of precision; usually it is sufficient to know that the model has coalesced, without knowing whether that happened in the current tick or in a recent preceding tick.

@@ -1307,5 +1315,27 @@

A user-defined integer value.  The value of tag is carried over automatically from the original Mutation object.  Apart from that, the value of tag is not used by SLiM; it is free for you to use.

5.18.2  Substitution methods


+

5.19  Class Trait

+

5.19.1  Trait properties

+

baselineOffset <–> (float$)

+

The baseline offset for the trait.  This value is combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.

+

directFitnessEffect <–> (logical$)

+

A logical flag indicating whether the trait has a direct fitness effect or not.  If T, the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component.  If F, the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a “fitness function”), or the trait might have other effects that are not obviously related to fitness at all.

+

index => (integer$)

+

The index of the trait in the vector of traits kept by the species.  The first trait defined in a species is at index 0, and subsequent traits count upwards from there.  The index of a trait is often used to refer to the trait, so it is important.  A global constant is defined for every trait, using each trait’s name, that provides the index of each trait, so this property will probably rarely be needed.

+

individualOffsetMean <–> (float$)

+

The mean for the normal distribution from which individual offsets are drawn.  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetSD property.

+

individualOffsetSD <–> (float$)

+

The standard deviation for the normal distribution from which individual offsets are drawn.  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetMean property.

+

name => (string$)

+

The name of the trait, as given to initializeTrait().  The default trait that is provided if no trait is explicitly defined has a name that is the name of the species plus a T; so for a single-species model, the default trait will generally be named simT.  The name of a trait can sometimes be used to refer to the trait, and is visible in SLiMgui.

+

species => (object<Species>$)

+

The species to which the target object belongs.

+

tag <–> (integer$)

+

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.

+

type => (string$)

+

The type of the trait, as a string.  In the present design, this will be either "multiplicative" or "additive".

+

5.19.2  Trait methods

+


diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 751be754..1a7e681d 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -144,6 +144,14 @@

The tickModulo and tickPhase parameters determine the activation schedule for the species.  The active property of the species will be set to T (thus activating the species) every tickModulo ticks, beginning in tick tickPhase.  (However, when the species is activated in a given tick, the skipTick() method may still be called in a first() event to deactivate it.)  See the active property of Species for more details.

The avatar parameter, if not "", sets a string value used to represent the species graphically, particularly in SLiMgui but perhaps in other contexts also.  The avatar should generally be a single character – usually an emoji corresponding to the species, such as "🦊" for foxes or "🐭" for mice.  If avatar is the empty string, "", SLiMgui will choose a default avatar.

The color parameter, if not "", sets a string color value used to represent the species in SLiMgui.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB" (see the Eidos manual for details).  If color is the empty string, "", SLiMgui will choose a default color.

+

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [logical$ directFitnessEffect = F])

+

Calling this function, added in SLiM 5.1, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

+

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not conflict with any other trait in any species in the model, it must not conflict with any global variable or constant, and it must not conflict with the name of any existing property on the Individual class.  The second requirement is because, after the new trait is created, a new global constant is defined that represents the trait’s index within the species, for quick reference to the trait in various contexts.  The third requirement is because, after the new trait is created, a new property is added to individuals of the species, with the same name as the new trait, that allows the trait values of individuals to be accessed directly as properties.  For example, if the new trait is named myTrait, a new global constant myTrait would be defined as myTrait’s index in the species, and access to an individual’s trait value would be possible through the property individual.myTrait.

+

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).

+

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive traits, such that the baseline offset has no effect upon the trait value.

+

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  As for the baseline offset, the individual offset mean defaults (if NULL is passed) to 1.0 for multiplicative traits, 0.0 for additive traits, to produce no effect.  The default standard deviation for the individual offset, if NULL is passed, is 0.0.  If NULL is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.

+

Finally, the directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

+

The use of the initializeTrait() function is optional.  If it is not called, a new Trait object will be created automatically, with a name generated from the species name plus a "T"; typically, then, the name is simT, except in multispecies models.  This default trait is configured to be multiplicative, with default values for the other parameters except directFitnessEffect, which is T for the default trait.  This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.1.  The creation of the default trait occurs as a side effect of the first call to initializeMutationType(), if initializeTrait() has not already been called.

(void)initializeTreeSeq([logical$ recordMutations = T], [Nif$ simplificationRatio = NULL], [Ni$ simplificationInterval = NULL], [logical$ checkCoalescence = F], [logical$ runCrosschecks = F], [logical$ retainCoalescentOnly = T], [Ns$ timeUnit = NULL])

Configure options for tree sequence recording.  Calling this function turns on tree sequence recording, as a side effect, for later reconstruction of the simulation’s evolutionary dynamics; if you do not want tree sequence recording to be enabled, do not call this function.  Note that tree-sequence recording internally uses SLiM’s “pedigree tracking” feature to uniquely identify individuals and haplosomes; however, if you want to use pedigree tracking in your script you must still enable it yourself with initializeSLiMOptions(keepPedigrees=T).  A separate tree sequence will be recorded for each chromosome in the simulation, as configured with initializeChromosome().

The recordMutations flag controls whether information about individual mutations is recorded or not.  Such recording takes time and memory, and so can be turned off if only the tree sequence itself is needed, but it is turned on by default since mutation recording is generally useful.

diff --git a/SLiM.xcodeproj/project.pbxproj b/SLiM.xcodeproj/project.pbxproj index e07368e6..660096d0 100644 --- a/SLiM.xcodeproj/project.pbxproj +++ b/SLiM.xcodeproj/project.pbxproj @@ -1600,6 +1600,11 @@ 98DB3D6F1E6122AE00E2C200 /* interaction_type.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */; }; 98DB3D701E6122AE00E2C200 /* interaction_type.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */; }; 98DB3D711E6122AE00E2C200 /* interaction_type.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */; }; + 98DC5A152E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; + 98DC5A162E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; + 98DC5A172E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; + 98DC5A182E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; + 98DC5A192E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; 98DC9841289986B300160DD8 /* GitSHA1_Xcode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC983D289986B300160DD8 /* GitSHA1_Xcode.cpp */; }; 98DD5F022155B857009062EE /* change_folder.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98DD5F002155B857009062EE /* change_folder.pdf */; }; 98DD5F032155B857009062EE /* change_folder_H.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98DD5F012155B857009062EE /* change_folder_H.pdf */; }; @@ -2137,6 +2142,8 @@ 98D7ED2D28CE58FC00DEAAC4 /* slim_multi */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = slim_multi; sourceTree = BUILT_PRODUCTS_DIR; }; 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = interaction_type.cpp; sourceTree = ""; }; 98DB3D6E1E6122AE00E2C200 /* interaction_type.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = interaction_type.h; sourceTree = ""; }; + 98DC5A132E0C4A5900398F6B /* trait.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = trait.cpp; sourceTree = ""; }; + 98DC5A142E0C4A5900398F6B /* trait.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = trait.h; sourceTree = ""; }; 98DC9838289986B300160DD8 /* GitSHA1.cpp.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GitSHA1.cpp.in; sourceTree = ""; }; 98DC9839289986B300160DD8 /* GetGitRevisionDescription.cmake */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GetGitRevisionDescription.cmake; sourceTree = ""; }; 98DC983A289986B300160DD8 /* GetGitRevisionDescription.cmake.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GetGitRevisionDescription.cmake.in; sourceTree = ""; }; @@ -2444,6 +2451,8 @@ 9878A93E1A4E57E70007B9D6 /* species.h */, 9878A93D1A4E57E70007B9D6 /* species.cpp */, 98AC617924BA34ED0001914C /* species_eidos.cpp */, + 98DC5A142E0C4A5900398F6B /* trait.h */, + 98DC5A132E0C4A5900398F6B /* trait.cpp */, 98E9A6981A3CD52A000AD4FC /* chromosome.h */, 98E9A6971A3CD52A000AD4FC /* chromosome.cpp */, 98E9A6AD1A3CD5D3000AD4FC /* population.h */, @@ -3865,6 +3874,7 @@ 98C821271C7A980000548839 /* message.c in Sources */, 984824F1210B9F23002402A5 /* dtrsv.c in Sources */, 98C821421C7A980000548839 /* infnan.c in Sources */, + 98DC5A152E0C4A5900398F6B /* trait.cpp in Sources */, 9807662924493A8F00F6CBB4 /* crc32.c in Sources */, 98C8213F1C7A980000548839 /* zeta.c in Sources */, 9890D1ED27136BB7001EAE98 /* eidos_class_DataFrame.cpp in Sources */, @@ -4035,6 +4045,7 @@ 9861526D2B167B4E0083E68F /* mvgauss.c in Sources */, 986152622B167B4E0083E68F /* blas.c in Sources */, 986152392B167B4E0083E68F /* log.c in Sources */, + 98DC5A182E0C4A5900398F6B /* trait.cpp in Sources */, 9823568D252FE61A0096A745 /* eidos_class_Image.cpp in Sources */, 986151ED2B167A380083E68F /* eidos_functions_values.cpp in Sources */, 9861523F2B167B4E0083E68F /* inline.c in Sources */, @@ -4264,6 +4275,7 @@ 98CF522F294A3FC900557BBA /* eidos_functions_matrices.cpp in Sources */, 98CF5230294A3FC900557BBA /* gauss.c in Sources */, 98CF5231294A3FC900557BBA /* EidosHelpController.mm in Sources */, + 98DC5A172E0C4A5900398F6B /* trait.cpp in Sources */, 98CF5232294A3FC900557BBA /* community_eidos.cpp in Sources */, 98CF5233294A3FC900557BBA /* lodepng.cpp in Sources */, 98CF5234294A3FC900557BBA /* slim_test_other.cpp in Sources */, @@ -4603,6 +4615,7 @@ 981DC35D28E26F8B000ABE91 /* eidos_functions_matrices.cpp in Sources */, 9876E5FD1ED559A600FF9762 /* gauss.c in Sources */, 982556651BA450980054CB3F /* EidosHelpController.mm in Sources */, + 98DC5A162E0C4A5900398F6B /* trait.cpp in Sources */, 9836868227CD72E900683639 /* community_eidos.cpp in Sources */, 98235683252FDCF50096A745 /* lodepng.cpp in Sources */, 9807C0F924BA21E3008CC658 /* slim_test_other.cpp in Sources */, @@ -5016,6 +5029,7 @@ 98D7ED1928CE58FC00DEAAC4 /* inline.c in Sources */, 98D7ED1A28CE58FC00DEAAC4 /* message.c in Sources */, 98D7ED1B28CE58FC00DEAAC4 /* dtrsv.c in Sources */, + 98DC5A192E0C4A5900398F6B /* trait.cpp in Sources */, 98D7ED1C28CE58FC00DEAAC4 /* infnan.c in Sources */, 98D7ED1D28CE58FC00DEAAC4 /* crc32.c in Sources */, 98D7ED1E28CE58FC00DEAAC4 /* zeta.c in Sources */, diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 48f1e452..f41ebda0 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -942,6 +942,14 @@ The recombination intervals are normally a constant in simulations, so be sure y \f4\fs20 objects defined in the simulation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 allTraits => (object)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 All of the +\f3\fs18 Trait +\f4\fs20 objects defined in the simulation (in species declaration order, primarily, and in order of their index within a species, secondarily).\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 cycleStage => (string$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -1795,6 +1803,7 @@ Beginning in SLiM 2.1, this is a class method, not an instance method. This mea \f4\fs20 method \f3\fs18 recalculateFitness() \f4\fs20 \'96 but see the documentation of that method for caveats.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf2 Note that in nonWF models that use tree-sequence recording, mutations cannot be added to an individual after the tick in which the individual is created (i.e., when the \f3\fs18 age \f4\fs20 of the individual is greater than @@ -1905,6 +1914,7 @@ Beginning in SLiM 2.1, this is a class method, not an instance method. This mea \f4\fs20 method \f3\fs18 recalculateFitness() \f4\fs20 \'96 but see the documentation of that method for caveats.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf2 Note that in nonWF models that use tree-sequence recording, mutations cannot be added to an individual after the tick in which the individual is created (i.e., when the \f3\fs18 age \f4\fs20 of the individual is greater than @@ -2562,9 +2572,8 @@ The \f4\fs20 subfield (or a generation of origin \f3\fs18 GO \f4\fs20 field, which was the SLiM convention before SLiM 4), the current tick will be used.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f3\fs18 \cf2 REF +\f3\fs18 REF \f4\fs20 and \f3\fs18 ALT \f4\fs20 must always be comprised of simple nucleotides ( @@ -5409,7 +5418,8 @@ See \f4\fs20 callback has changed in such a way that previously calculated interaction strengths are no longer correct, \f3\fs18 unevaluate() \f4\fs20 allows the interaction to begin again from scratch.\ -\expnd0\expndtw0\kerning0 +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 \expnd0\expndtw0\kerning0 In WF models, all interactions are automatically reset to an unevaluated state at the moment when the new offspring generation becomes the parental generation (at step 4 in the tick cycle).\ In nonWF models, all interactions are automatically reset to an unevaluated state twice per tick: immediately after \f3\fs18 reproduction() @@ -8394,6 +8404,14 @@ The spatial periodicity of the simulation for this species, as specified in \f3\fs18 Dictionary \f4\fs20 class; see the Eidos manual)\cf0 , for another way of attaching state to the simulation. \f5 \ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 traits => (object)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The +\f3\fs18 Trait +\f4\fs20 objects defined in the species. These are guaranteed to be in sorted order by their index (i.e., by the order in which they were defined).\ \pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 \f1\i\fs22 \cf0 5.16.2 @@ -9312,7 +9330,8 @@ Beginning with SLiM 3.3, the \f4\i0 include the nucleotides associated with any nucleotide-based mutations; the \f3\fs18 ancestralNucleotides \f4\fs20 flag governs only the ancestral sequence.\ -\kerning1\expnd0\expndtw0 Beginning with SLiM 3.5, the +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 \kerning1\expnd0\expndtw0 Beginning with SLiM 3.5, the \f3\fs18 pedigreeIDs \f4\fs20 parameter may be used to request that pedigree IDs be written out (and read in by \f3\fs18 readFromPopulationFile() @@ -9451,7 +9470,8 @@ Beginning with SLiM 5.0, the \f4\fs20 , etc.) will be defined to refer to the new \f3\fs18 Subpopulation \f4\fs20 objects loaded from the file. Note that fitness values are not calculated as a side effect of this call (because the simulation will often need to evaluate interactions or modify other state prior to doing so).\ -\expnd0\expndtw0\kerning0 +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 \expnd0\expndtw0\kerning0 In SLiM 2.3 and later when using the WF model, calling \f3\fs18 readFromPopulationFile() \f4\fs20 from any context other than a @@ -9470,7 +9490,8 @@ In SLiM 3.0 when using the nonWF model, calling \f4\fs20 event is almost always correct in nonWF models, so that fitness values can be automatically recalculated by SLiM at the usual time in the tick cycle without the need to force their recalculation (see comments on \f3\fs18 recalculateFitness() \f4\fs20 ).\ -\kerning1\expnd0\expndtw0 This method changes the tick and cycle counters to the tick and cycle read from the file. If you do not want these counters to be changed, you can change them back after reading, by setting +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 \kerning1\expnd0\expndtw0 This method changes the tick and cycle counters to the tick and cycle read from the file. If you do not want these counters to be changed, you can change them back after reading, by setting \f3\fs18 community.tick \f4\fs20 and \f3\fs18 sim.cycle @@ -9491,12 +9512,14 @@ Any changes made to the structure of the species (mutation types, genomic elemen \f3\fs18 tag \f4\fs20 values of individuals; if a given option is enabled and the corresponding information is saved, then that information will be restored, otherwise it will not be.\ As of SLiM 2.3, this method will read and restore the spatial positions of individuals if that information is present in the output file and the species has enabled continuous space. If spatial positions are present in the output file but the species has not enabled continuous space (or the number of spatial dimensions does not match), an error will result. If the species has enabled continuous space but spatial positions are not present in the output file, the spatial positions of the individuals read will be undefined, but an error is not raised.\ -\expnd0\expndtw0\kerning0 +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 \expnd0\expndtw0\kerning0 As of SLiM 3.0, this method will read and restore the ages of individuals if that information is present in the output file and the simulation is based upon the nonWF model. If ages are present but the simulation uses a WF model, an error will result; the WF model does not use age information. If ages are not present but the simulation uses a nonWF model, an error will also result; the nonWF model requires age information.\ As of SLiM 3.3, this method will restore the nucleotides of nucleotide-based mutations, and will restore the ancestral nucleotide sequence, if that information is present in the output file. Loading an output file that contains nucleotide information in a non-nucleotide-based model, and \f1\i vice versa \f4\i0 , will produce an error.\ -\kerning1\expnd0\expndtw0 As of SLiM 3.5, this method will read and restore the pedigree IDs of individuals and haplosomes if that information is present in the output file (as requested with +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 \kerning1\expnd0\expndtw0 As of SLiM 3.5, this method will read and restore the pedigree IDs of individuals and haplosomes if that information is present in the output file (as requested with \f3\fs18 outputFull(pedigreeIDs=T) \f4\fs20 ) \f1\i and @@ -10115,6 +10138,34 @@ This method is shorthand for getting the \f4\fs20 .\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(object)traitsWithIndices(integer\'a0indices)\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns a vector of +\f3\fs18 Trait +\f4\fs20 objects corresponding to the trait indices supplied in +\f3\fs18 indices +\f4\fs20 , in the same order. If any index in +\f3\fs18 indices +\f4\fs20 does not correspond to a trait in the target species, an error will be raised. See also +\f3\fs18 traitsWithNames() +\f4\fs20 .\ +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(object)traitsWithNames(string\'a0names)\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns a vector of +\f3\fs18 Trait +\f4\fs20 objects corresponding to the trait names supplied in +\f3\fs18 names +\f4\fs20 , in the same order. If any name in +\f3\fs18 names +\f4\fs20 does not correspond to a trait in the target species, an error will be raised. See also +\f3\fs18 traitsWithIndices() +\f4\fs20 .\ +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \expnd0\expndtw0\kerning0 \'96\'a0(logical$)treeSeqCoalesced(void)\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 @@ -13352,5 +13403,105 @@ nucleotide <\'96> (string$)\ \f1\fs22 methods\ \pard\pardeftab397\fi274\ri720\sb40\sa40\partightenfactor0 +\f5\i0 \cf0 \ +\pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 + +\f0\b \cf0 5.19 Class Trait\ +\pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 + +\f1\i\b0 \cf0 5.19.1 +\f2\fs18 Trait +\f1\fs22 properties\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\i0\fs18 \cf2 baselineOffset <\'96> (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The baseline offset for the trait. This value is combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 directFitnessEffect <\'96> (logical$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 A +\f3\fs18 logical +\f4\fs20 flag indicating whether the trait has a direct fitness effect or not. If +\f3\fs18 T +\f4\fs20 , the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component. If +\f3\fs18 F +\f4\fs20 , the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a \'93fitness function\'94), or the trait might have other effects that are not obviously related to fitness at all.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 index => (integer$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The index of the trait in the vector of traits kept by the species. The first trait defined in a species is at index +\f3\fs18 0 +\f4\fs20 , and subsequent traits count upwards from there. The index of a trait is often used to refer to the trait, so it is important. A global constant is defined for every trait, using each trait\'92s name, that provides the index of each trait, so this property will probably rarely be needed.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 individualOffsetMean <\'96> (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The mean for the normal distribution from which individual offsets are drawn. Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the +\f3\fs18 individualOffsetSD +\f4\fs20 property.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 individualOffsetSD <\'96> (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The standard deviation for the normal distribution from which individual offsets are drawn. Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the +\f3\fs18 individualOffsetMean +\f4\fs20 property.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 name => (string$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The name of the trait, as given to +\f3\fs18 initializeTrait() +\f4\fs20 . The default trait that is provided if no trait is explicitly defined has a name that is the name of the species plus a +\f3\fs18 T +\f4\fs20 ; so for a single-species model, the default trait will generally be named +\f3\fs18 simT +\f4\fs20 . The name of a trait can sometimes be used to refer to the trait, and is visible in SLiMgui.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 species => (object$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The species to which the target object belongs.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 tag <\'96> (integer$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 A user-defined +\f3\fs18 integer +\f4\fs20 value. The value of +\f3\fs18 tag +\f4\fs20 is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code. The value of +\f3\fs18 tag +\f4\fs20 is not used by SLiM; it is free for you to use.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 type => (string$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The type of the trait, as a +\f3\fs18 string +\f4\fs20 . In the present design, this will be either +\f3\fs18 "multiplicative" +\f4\fs20 or +\f3\fs18 "additive" +\f4\fs20 .\ +\pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 + +\f1\i\fs22 \cf0 5.19.2 +\f2\fs18 Trait +\f1\fs22 methods\ +\pard\pardeftab397\fi274\ri720\sb40\sa40\partightenfactor0 + \f5\i0 \cf0 \ } \ No newline at end of file diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index cf13236a..40b185de 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -1460,6 +1460,94 @@ The \f2\fs20 , SLiMgui will choose a default color.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f1\fs18 \cf2 (object$)initializeTrait(string$\'a0name, string$\'a0type, [Nf$\'a0baselineOffset\'a0=\'a0NULL], [Nf$\'a0individualOffsetMean\'a0=\'a0NULL], [Nf$\'a0individualOffsetSD\'a0=\'a0NULL], [logical$\'a0directFitnessEffect\'a0=\'a0F])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f2\fs20 \cf2 Calling this function, added in SLiM 5.1, configures a phenotypic trait in the species being initialized. The new +\f1\fs18 Trait +\f2\fs20 object is returned. For more details on the way that traits work in SLiM, beyond what is given below, see the +\f1\fs18 Trait +\f2\fs20 class documentation.\ +The +\f1\fs18 name +\f2\fs20 parameter gives the name of the new trait. This may be any (non-empty) string, except that it must not conflict with any other trait in any species in the model, it must not conflict with any global variable or constant, and it must not conflict with the name of any existing property on the +\f1\fs18 Individual +\f2\fs20 class. The second requirement is because, after the new trait is created, a new global constant is defined that represents the trait\'92s index within the species, for quick reference to the trait in various contexts. The third requirement is because, after the new trait is created, a new property is added to individuals of the species, with the same name as the new trait, that allows the trait values of individuals to be accessed directly as properties. For example, if the new trait is named +\f1\fs18 myTrait +\f2\fs20 , a new global constant +\f1\fs18 myTrait +\f2\fs20 would be defined as +\f1\fs18 myTrait +\f2\fs20 \'92s index in the species, and access to an individual\'92s trait value would be possible through the property +\f1\fs18 individual.myTrait +\f2\fs20 .\ +The +\f1\fs18 type +\f2\fs20 parameter gives the type of trait to be created, as a +\f1\fs18 string +\f2\fs20 value. This should be either +\f1\fs18 "multiplicative" +\f2\fs20 , if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or +\f1\fs18 "additive" +\f2\fs20 , if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).\ +The +\f1\fs18 baselineOffset +\f2\fs20 parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual. If +\f1\fs18 NULL +\f2\fs20 is passed, the default baseline offset is +\f1\fs18 1.0 +\f2\fs20 for multiplicative traits, +\f1\fs18 0.0 +\f2\fs20 for additive traits, such that the baseline offset has no effect upon the trait value.\ +The +\f1\fs18 individualOffsetMean +\f2\fs20 and +\f1\fs18 individualOffsetSD +\f2\fs20 parameters together define a normal distribution from which individual offsets are drawn to provide what is often called \'93environmental variance\'94 or \'93developmental noise\'94. As for the baseline offset, the individual offset mean defaults (if +\f1\fs18 NULL +\f2\fs20 is passed) to +\f1\fs18 1.0 +\f2\fs20 for multiplicative traits, +\f1\fs18 0.0 +\f2\fs20 for additive traits, to produce no effect. The default standard deviation for the individual offset, if +\f1\fs18 NULL +\f2\fs20 is passed, is +\f1\fs18 0.0 +\f2\fs20 . If +\f1\fs18 NULL +\f2\fs20 is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.\ +Finally, the +\f1\fs18 directFitnessEffect +\f2\fs20 parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual. This will typically be +\f1\fs18 T +\f2\fs20 (the default) in population-genetics models where the product of all mutation effects ( +\f1\fs18 1+s +\f2\fs20 or +\f1\fs18 1+hs +\f2\fs20 for each mutation) is used as the fitness of the individual, but will typically be +\f1\fs18 F +\f2\fs20 in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function. It would also be +\f1\fs18 F +\f2\fs20 for any trait that affects an aspect of the individual other than fitness \'96 dispersal distance, for example, or aggression.\ +The use of the +\f1\fs18 initializeTrait() +\f2\fs20 function is optional. If it is not called, a new +\f1\fs18 Trait +\f2\fs20 object will be created automatically, with a name generated from the species name plus a +\f1\fs18 "T" +\f2\fs20 ; typically, then, the name is +\f1\fs18 simT +\f2\fs20 , except in multispecies models. This default trait is configured to be multiplicative, with default values for the other parameters except +\f1\fs18 directFitnessEffect +\f2\fs20 , which is +\f1\fs18 T +\f2\fs20 for the default trait. This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.1. The creation of the default trait occurs as a side effect of the first call to +\f1\fs18 initializeMutationType() +\f2\fs20 , if +\f1\fs18 initializeTrait() +\f2\fs20 has not already been called.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f1\fs18 \cf2 \expnd0\expndtw0\kerning0 (void)initializeTreeSeq([logical$\'a0recordMutations\'a0=\'a0T], [Nif$\'a0simplificationRatio\'a0=\'a0NULL], [Ni$\'a0simplificationInterval\'a0=\'a0NULL], [logical$\'a0checkCoalescence\'a0=\'a0F], [logical$\'a0runCrosschecks\'a0=\'a0F], [logical$\'a0\kerning1\expnd0\expndtw0 retainCoalescentOnly\expnd0\expndtw0\kerning0 \'a0=\'a0T]\kerning1\expnd0\expndtw0 , [Ns$\'a0timeUnit\'a0=\'a0NULL]\expnd0\expndtw0\kerning0 diff --git a/VERSIONS b/VERSIONS index 364c7e81..23f878b0 100644 --- a/VERSIONS +++ b/VERSIONS @@ -16,6 +16,23 @@ development head (in the master branch): add chromosomeSubposition property to Haplosome, providing whether the haplosome is index 0 or 1 within its individual, within the set of haplosomes for its associated chromosome add a (numeric)sign(numeric x) function that returns the sign (-1, 0, 1) of each element +multitrait branch: + add new Eidos SLiM class, Trait + add (object$)initializeTrait(s$ name, s$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [l$ directFitnessEffect = F]) + add Species property traits => (object) to simply get all traits defined for a species + add Species methods – (object)traitsWithIndices(integer indices) and – (object)traitsWithNames(string names) + add Trait properties: + baselineOffset <-> (float$) + directFitnessEffect <-> (logical$) + index => (integer$) + individualOffsetMean <-> (float$) + individualOffsetSD <-> (float$) + name => (string$) + species => (object$) + tag <-> (integer$) + type => (string$) + add Community property allTraits => (object) + version 5.0 (Eidos version 4.0): multi-chromosome transition: diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index 8ef389a2..e852f4c7 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -20,6 +20,7 @@ #include "community.h" +#include "trait.h" #include "haplosome.h" #include "individual.h" #include "subpopulation.h" @@ -88,6 +89,7 @@ EidosValue_SP Community::ContextDefinedFunctionDispatch(const std::string &p_fun else if (p_function_name.compare(gStr_initializeMutationType) == 0) return active_species_->ExecuteContextFunction_initializeMutationType(p_function_name, p_arguments, p_interpreter); // NOLINT(*-branch-clone) : intentional consecutive branches else if (p_function_name.compare(gStr_initializeMutationTypeNuc) == 0) return active_species_->ExecuteContextFunction_initializeMutationType(p_function_name, p_arguments, p_interpreter); else if (p_function_name.compare(gStr_initializeRecombinationRate) == 0) return active_species_->ExecuteContextFunction_initializeRecombinationRate(p_function_name, p_arguments, p_interpreter); + else if (p_function_name.compare(gStr_initializeTrait) == 0) return active_species_->ExecuteContextFunction_initializeTrait(p_function_name, p_arguments, p_interpreter); else if (p_function_name.compare(gStr_initializeChromosome) == 0) return active_species_->ExecuteContextFunction_initializeChromosome(p_function_name, p_arguments, p_interpreter); else if (p_function_name.compare(gStr_initializeGeneConversion) == 0) return active_species_->ExecuteContextFunction_initializeGeneConversion(p_function_name, p_arguments, p_interpreter); else if (p_function_name.compare(gStr_initializeMutationRate) == 0) return active_species_->ExecuteContextFunction_initializeMutationRate(p_function_name, p_arguments, p_interpreter); @@ -123,6 +125,7 @@ const std::vector *Community::ZeroTickFunctionSignat ->AddIntString_S("id")->AddNumeric_S("dominanceCoeff")->AddString_S("distributionType")->AddEllipsis()); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeRecombinationRate, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric("rates")->AddInt_ON("ends", gStaticEidosValueNULL)->AddString_OS("sex", gStaticEidosValue_StringAsterisk)); + sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeTrait, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class, "SLiM"))->AddString_S("name")->AddString_S("type")->AddFloat_OSN("baselineOffset", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetMean", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetSD", gStaticEidosValueNULL)->AddLogical_OS("directFitnessEffect", gStaticEidosValue_LogicalF)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeChromosome, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class, "SLiM"))->AddInt_S("id")->AddInt_OSN("length", gStaticEidosValueNULL)->AddString_OS("type", gStaticEidosValue_StringA)->AddString_OSN("symbol", gStaticEidosValueNULL)->AddString_OSN("name", gStaticEidosValueNULL)->AddInt_OS("mutationRuns", gStaticEidosValue_Integer0)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeGeneConversion, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric_S("nonCrossoverFraction")->AddNumeric_S("meanLength")->AddNumeric_S("simpleConversionFraction")->AddNumeric_OS("bias", gStaticEidosValue_Integer0)->AddLogical_OS("redrawLengthsOnFailure", gStaticEidosValue_LogicalF)); @@ -385,6 +388,17 @@ EidosValue_SP Community::GetProperty(EidosGlobalStringID p_property_id) return result_SP; } + case gID_allTraits: + { + EidosValue_Object *vec = new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Trait_Class); + EidosValue_SP result_SP = EidosValue_SP(vec); + + for (auto species : all_species_) + for (auto trait : species->Traits()) + vec->push_object_element_RR(trait); + + return result_SP; + } case gID_logFiles: { EidosValue_Object *vec = new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_LogFile_Class); @@ -1346,6 +1360,7 @@ const std::vector *Community_Class::Properties(void) properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_allScriptBlocks, true, kEidosValueMaskObject, gSLiM_SLiMEidosBlock_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_allSpecies, true, kEidosValueMaskObject, gSLiM_Species_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_allSubpopulations, true, kEidosValueMaskObject, gSLiM_Subpopulation_Class))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_allTraits, true, kEidosValueMaskObject, gSLiM_Trait_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_logFiles, true, kEidosValueMaskObject, gSLiM_LogFile_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_modelType, true, kEidosValueMaskString | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tick, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); diff --git a/core/individual.cpp b/core/individual.cpp index 9527c0d8..a651a867 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -5159,6 +5159,45 @@ EidosValue_SP Individual_Class::ExecuteMethod_setSpatialPosition(EidosGlobalStri return gStaticEidosValueVOID; } +// In these methods we implement a special behavior: you can do individual.traitName to +// access the value for a trait. We do a dynamic lookup from the trait name here. + +EidosValue_SP Individual_Class::GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size) const +{ + const Individual *const *individuals = (const Individual *const *)p_targets; + + Species *species = Community::SpeciesForIndividualsVector(individuals, (int)p_targets_size); + Trait *trait = species->TraitFromStringID(p_property_id); + + if (trait) + { + // We got a hit, but don't know what to do with it for now + EIDOS_TERMINATION << "ERROR (Individual_Class::GetProperty_NO_SIGNATURE): trait " << trait->Name() << " cannot be accessed (FIXME)." << EidosTerminate(); + } + + return super::GetProperty_NO_SIGNATURE(p_property_id, p_targets, p_targets_size); +} + +void Individual_Class::SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size, const EidosValue &p_value) const +{ + const Individual *const *individuals = (const Individual *const *)p_targets; + + Species *species = Community::SpeciesForIndividualsVector(individuals, (int)p_targets_size); + Trait *trait = species->TraitFromStringID(p_property_id); + + if (trait) + { + // Eidos did not type-check for us, because there is no signature! We have to check it ourselves. + if (p_value.Type() != EidosValueType::kValueFloat) + EIDOS_TERMINATION << "ERROR (Individual_Class::SetProperty_NO_SIGNATURE): assigned value must be of type float for trait-value property " << trait->Name() << "." << EidosTerminate(); + + // We got a hit, but don't know what to do with it for now + EIDOS_TERMINATION << "ERROR (Individual_Class::GetProperty_NO_SIGNATURE): trait " << trait->Name() << " cannot be accessed (FIXME)." << EidosTerminate(); + } + + return super::SetProperty_NO_SIGNATURE(p_property_id, p_targets, p_targets_size, p_value); +} + diff --git a/core/individual.h b/core/individual.h index b337c883..cf3084fa 100644 --- a/core/individual.h +++ b/core/individual.h @@ -406,6 +406,10 @@ class Individual_Class : public EidosDictionaryUnretained_Class EidosValue_SP ExecuteMethod_outputIndividualsToVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_readIndividualsFromVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_setSpatialPosition(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + + // These special accessors are implemented to handle the properties on Individual for defined traits + virtual EidosValue_SP GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size) const override; + virtual void SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size, const EidosValue &p_value) const override; }; diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 03193f31..9e2ceebb 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -20,6 +20,7 @@ #include "slim_globals.h" +#include "trait.h" #include "chromosome.h" #include "individual.h" #include "interaction_type.h" @@ -76,6 +77,7 @@ void SLiM_WarmUp(void) // Create the global class objects for all SLiM Eidos classes, from superclass to subclass // This breaks encapsulation, kind of, but it needs to be done here, in order, so that superclass objects exist, // and so that the global string names for the classes have already been set up by C++'s static initialization + gSLiM_Trait_Class = new Trait_Class( gStr_Trait, gEidosDictionaryRetained_Class); gSLiM_Chromosome_Class = new Chromosome_Class( gStr_Chromosome, gEidosDictionaryRetained_Class); gSLiM_Individual_Class = new Individual_Class( gEidosStr_Individual, gEidosDictionaryUnretained_Class); gSLiM_InteractionType_Class = new InteractionType_Class( gStr_InteractionType, gEidosDictionaryUnretained_Class); @@ -1178,6 +1180,7 @@ const std::string &gStr_initializeGenomicElement = EidosRegisteredString("initia const std::string &gStr_initializeGenomicElementType = EidosRegisteredString("initializeGenomicElementType", gID_initializeGenomicElementType); const std::string &gStr_initializeMutationType = EidosRegisteredString("initializeMutationType", gID_initializeMutationType); const std::string &gStr_initializeMutationTypeNuc = EidosRegisteredString("initializeMutationTypeNuc", gID_initializeMutationTypeNuc); +const std::string &gStr_initializeTrait = EidosRegisteredString("initializeTrait", gID_initializeTrait); const std::string &gStr_initializeChromosome = EidosRegisteredString("initializeChromosome", gID_initializeChromosome); const std::string &gStr_initializeGeneConversion = EidosRegisteredString("initializeGeneConversion", gID_initializeGeneConversion); const std::string &gStr_initializeMutationRate = EidosRegisteredString("initializeMutationRate", gID_initializeMutationRate); @@ -1191,6 +1194,10 @@ const std::string &gStr_initializeSLiMModelType = EidosRegisteredString("initial const std::string &gStr_initializeInteractionType = EidosRegisteredString("initializeInteractionType", gID_initializeInteractionType); // mostly property names +const std::string &gStr_baselineOffset = EidosRegisteredString("baselineOffset", gID_baselineOffset); +const std::string &gStr_individualOffsetMean = EidosRegisteredString("individualOffsetMean", gID_individualOffsetMean); +const std::string &gStr_individualOffsetSD = EidosRegisteredString("individualOffsetSD", gID_individualOffsetSD); +const std::string &gStr_directFitnessEffect = EidosRegisteredString("directFitnessEffect", gID_directFitnessEffect); const std::string &gStr_genomicElements = EidosRegisteredString("genomicElements", gID_genomicElements); const std::string &gStr_lastPosition = EidosRegisteredString("lastPosition", gID_lastPosition); const std::string &gStr_hotspotEndPositions = EidosRegisteredString("hotspotEndPositions", gID_hotspotEndPositions); @@ -1263,8 +1270,10 @@ const std::string &gStr_allMutationTypes = EidosRegisteredString("allMutationTyp const std::string &gStr_allScriptBlocks = EidosRegisteredString("allScriptBlocks", gID_allScriptBlocks); const std::string &gStr_allSpecies = EidosRegisteredString("allSpecies", gID_allSpecies); const std::string &gStr_allSubpopulations = EidosRegisteredString("allSubpopulations", gID_allSubpopulations); +const std::string &gStr_allTraits = EidosRegisteredString("allTraits", gID_allTraits); const std::string &gStr_chromosome = EidosRegisteredString("chromosome", gID_chromosome); const std::string &gStr_chromosomes = EidosRegisteredString("chromosomes", gID_chromosomes); +const std::string &gStr_traits = EidosRegisteredString("traits", gID_traits); const std::string &gStr_genomicElementTypes = EidosRegisteredString("genomicElementTypes", gID_genomicElementTypes); const std::string &gStr_lifetimeReproductiveOutput = EidosRegisteredString("lifetimeReproductiveOutput", gID_lifetimeReproductiveOutput); const std::string &gStr_lifetimeReproductiveOutputM = EidosRegisteredString("lifetimeReproductiveOutputM", gID_lifetimeReproductiveOutputM); @@ -1379,6 +1388,8 @@ const std::string &gStr_addSubpopSplit = EidosRegisteredString("addSubpopSplit", const std::string &gStr_chromosomesOfType = EidosRegisteredString("chromosomesOfType", gID_chromosomesOfType); const std::string &gStr_chromosomesWithIDs = EidosRegisteredString("chromosomesWithIDs", gID_chromosomesWithIDs); const std::string &gStr_chromosomesWithSymbols = EidosRegisteredString("chromosomesWithSymbols", gID_chromosomesWithSymbols); +const std::string &gStr_traitsWithIndices = EidosRegisteredString("traitsWithIndices", gID_traitsWithIndices); +const std::string &gStr_traitsWithNames = EidosRegisteredString("traitsWithNames", gID_traitsWithNames); const std::string &gStr_estimatedLastTick = EidosRegisteredString("estimatedLastTick", gID_estimatedLastTick); const std::string &gStr_deregisterScriptBlock = EidosRegisteredString("deregisterScriptBlock", gID_deregisterScriptBlock); const std::string &gStr_genomicElementTypesWithIDs = EidosRegisteredString("genomicElementTypesWithIDs", gID_genomicElementTypesWithIDs); @@ -1550,6 +1561,7 @@ const std::string &gStr_text = EidosRegisteredString("text", gID_text); const std::string &gStr_title = EidosRegisteredString("title", gID_title); // mostly SLiM element types +const std::string &gStr_Trait = EidosRegisteredString("Trait", gID_Trait); const std::string &gStr_Chromosome = EidosRegisteredString("Chromosome", gID_Chromosome); //const std::string &gStr_Haplosome = EidosRegisteredString("Haplosome", gID_Haplosome); // in Eidos; see EidosValue_Object::EidosValue_Object() const std::string &gStr_GenomicElement = EidosRegisteredString("GenomicElement", gID_GenomicElement); diff --git a/core/slim_globals.h b/core/slim_globals.h index 9e1cb652..e252df2c 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -563,6 +563,12 @@ enum class SLiMCycleStage std::string StringForSLiMCycleStage(SLiMCycleStage p_stage); +// This enumeration represents the type of a trait: multiplicative or additive. +enum class TraitType : uint8_t { + kMultiplicative = 0, + kAdditive +}; + // This enumeration represents the type of a chromosome. Note that the sex of an individual cannot always be inferred // from chromosomal state, and the user is allowed to play games with null haplosomes; the chromosomes follow the sex // of the individual, the sex of the individual does not follow the chromosomes. See the initializeChromosome() doc. @@ -753,6 +759,7 @@ extern const std::string &gStr_initializeGenomicElement; extern const std::string &gStr_initializeGenomicElementType; extern const std::string &gStr_initializeMutationType; extern const std::string &gStr_initializeMutationTypeNuc; +extern const std::string &gStr_initializeTrait; extern const std::string &gStr_initializeChromosome; extern const std::string &gStr_initializeGeneConversion; extern const std::string &gStr_initializeMutationRate; @@ -765,6 +772,12 @@ extern const std::string &gStr_initializeTreeSeq; extern const std::string &gStr_initializeSLiMModelType; extern const std::string &gStr_initializeInteractionType; +//extern const std::string &gStr_type; now gEidosStr_type +extern const std::string &gStr_baselineOffset; +extern const std::string &gStr_individualOffsetMean; +extern const std::string &gStr_individualOffsetSD; +extern const std::string &gStr_directFitnessEffect; + extern const std::string &gStr_genomicElements; extern const std::string &gStr_lastPosition; extern const std::string &gStr_hotspotEndPositions; @@ -837,6 +850,7 @@ extern const std::string &gStr_allMutationTypes; extern const std::string &gStr_allScriptBlocks; extern const std::string &gStr_allSpecies; extern const std::string &gStr_allSubpopulations; +extern const std::string &gStr_allTraits; extern const std::string &gStr_chromosome; extern const std::string &gStr_chromosomes; extern const std::string &gStr_genomicElementTypes; @@ -862,6 +876,7 @@ extern const std::string &gStr_tagL1; extern const std::string &gStr_tagL2; extern const std::string &gStr_tagL3; extern const std::string &gStr_tagL4; +extern const std::string &gStr_traits; extern const std::string &gStr_migrant; extern const std::string &gStr_fitnessScaling; extern const std::string &gStr_firstMaleIndex; @@ -952,6 +967,8 @@ extern const std::string &gStr_addSubpopSplit; extern const std::string &gStr_chromosomesOfType; extern const std::string &gStr_chromosomesWithIDs; extern const std::string &gStr_chromosomesWithSymbols; +extern const std::string &gStr_traitsWithIndices; +extern const std::string &gStr_traitsWithNames; extern const std::string &gStr_estimatedLastTick; extern const std::string &gStr_deregisterScriptBlock; extern const std::string &gStr_genomicElementTypesWithIDs; @@ -1119,6 +1136,7 @@ extern const std::string &gStr_points; extern const std::string &gStr_text; extern const std::string &gStr_title; +extern const std::string &gStr_Trait; extern const std::string &gStr_Chromosome; //extern const std::string &gStr_Haplosome; // in Eidos; see EidosValue_Object::EidosValue_Object() extern const std::string &gStr_GenomicElement; @@ -1207,6 +1225,7 @@ enum _SLiMGlobalStringID : int { gID_initializeGenomicElementType, gID_initializeMutationType, gID_initializeMutationTypeNuc, + gID_initializeTrait, gID_initializeChromosome, gID_initializeGeneConversion, gID_initializeMutationRate, @@ -1219,6 +1238,11 @@ enum _SLiMGlobalStringID : int { gID_initializeSLiMModelType, gID_initializeInteractionType, + gID_baselineOffset, + gID_individualOffsetMean, + gID_individualOffsetSD, + gID_directFitnessEffect, + gID_genomicElements, gID_lastPosition, gID_hotspotEndPositions, @@ -1291,8 +1315,10 @@ enum _SLiMGlobalStringID : int { gID_allScriptBlocks, gID_allSpecies, gID_allSubpopulations, + gID_allTraits, gID_chromosome, gID_chromosomes, + gID_traits, gID_genomicElementTypes, gID_lifetimeReproductiveOutput, gID_lifetimeReproductiveOutputM, @@ -1405,6 +1431,8 @@ enum _SLiMGlobalStringID : int { gID_chromosomesOfType, gID_chromosomesWithIDs, gID_chromosomesWithSymbols, + gID_traitsWithIndices, + gID_traitsWithNames, gID_addSubpopSplit, gID_estimatedLastTick, gID_deregisterScriptBlock, @@ -1573,6 +1601,7 @@ enum _SLiMGlobalStringID : int { gID_text, gID_title, + gID_Trait, gID_Chromosome, gID_Haplosome, gID_GenomicElement, diff --git a/core/species.cpp b/core/species.cpp index b89c4a61..78d7e49d 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -514,6 +514,63 @@ void Species::GetChromosomeIndicesFromEidosValue(std::vectorIndex() != -1) + EIDOS_TERMINATION << "ERROR (Species::AddTrait): (internal error) attempt to add a trait with index != -1." << EidosTerminate(); + + std::string name = p_trait->Name(); + EidosGlobalStringID name_string_id = EidosStringRegistry::GlobalStringIDForString(name); + + // this is the main registry, and owns the retain count on every trait; it takes the caller's retain here + p_trait->SetIndex(traits_.size()); + traits_.push_back(p_trait); + + // these are secondary indices that do not keep a retain on the traits + trait_from_name.emplace(name, p_trait); + trait_from_string_id.emplace(name_string_id, p_trait); +} + // get one line of input, sanitizing by removing comments and whitespace; used only by Species::InitializePopulationFromTextFile void GetInputLine(std::istream &p_input_file, std::string &p_line); void GetInputLine(std::istream &p_input_file, std::string &p_line) @@ -2448,12 +2505,14 @@ std::vector Species::CallbackBlocksMatching(slim_tick_t p_tick, void Species::RunInitializeCallbacks(void) { // zero out the initialization check counts + // FIXME: doing this here is error-prone; the species object should zero-initialize all of this stuff instead! num_species_inits_ = 0; num_slimoptions_inits_ = 0; num_mutation_type_inits_ = 0; num_ge_type_inits_ = 0; num_sex_inits_ = 0; num_treeseq_inits_ = 0; + num_trait_inits_ = 0; num_chromosome_inits_ = 0; num_mutrate_inits_ = 0; @@ -2463,6 +2522,7 @@ void Species::RunInitializeCallbacks(void) num_ancseq_inits_ = 0; num_hotmap_inits_ = 0; + has_implicit_trait_ = false; has_implicit_chromosome_ = false; // execute initialize() callbacks, which should always have a tick of 0 set diff --git a/core/species.h b/core/species.h index 2b2b08aa..1cbd5950 100644 --- a/core/species.h +++ b/core/species.h @@ -35,6 +35,7 @@ #include "slim_globals.h" #include "population.h" #include "chromosome.h" +#include "trait.h" #include "eidos_value.h" #include "mutation_run.h" @@ -78,6 +79,9 @@ enum class SLiMFileFormat // There would be an upper limit of 256 anyway because Mutation uses uint8_t to keep the index of its chromosome #define SLIM_MAX_CHROMOSOMES 256 +// We have a defined maximum number of traits; it is not clear that this is necessary, however. FIXME MULTITRAIT +#define SLIM_MAX_TRAITS 256 + // TREE SEQUENCE RECORDING #pragma mark - @@ -202,6 +206,21 @@ class Species : public EidosDictionaryUnretained std::map mutation_types_; // OWNED POINTERS: this map is the owner of all allocated MutationType objects std::map genomic_element_types_; // OWNED POINTERS: this map is the owner of all allocated GenomicElementType objects + // for multiple traits, we now have a vector of pointers to Trait objects, as well as hash tables for quick + // lookup by name and by string ID; the latter is to make using trait names as properties on Individual fast +#if EIDOS_ROBIN_HOOD_HASHING + typedef robin_hood::unordered_flat_map TRAIT_NAME_HASH; + typedef robin_hood::unordered_flat_map TRAIT_STRID_HASH; +#elif STD_UNORDERED_MAP_HASHING + typedef std::unordered_map TRAIT_NAME_HASH; + typedef std::unordered_map TRAIT_STRID_HASH; +#endif + + // Trait state + std::vector traits_; // OWNED (retained); all our traits, in the order in which they were defined + TRAIT_NAME_HASH trait_from_name; // NOT OWNED; get a trait from a trait name quickly + TRAIT_STRID_HASH trait_from_string_id; // NOT OWNED; get a trait from a string ID quickly + bool mutation_stack_policy_changed_ = true; // when set, the stacking policy settings need to be checked for consistency // SEX ONLY: sex-related instance variables @@ -272,6 +291,8 @@ class Species : public EidosDictionaryUnretained int num_ge_type_inits_; // number of calls to initializeGenomicElementType() int num_sex_inits_; // SEX ONLY: number of calls to initializeSex() int num_treeseq_inits_; // number of calls to initializeTreeSeq() + int num_trait_inits_; // number of calls to initializeTrait() + bool has_implicit_trait_; // true if the model implicitly defines a trait, with no initializeTrait() call int num_chromosome_inits_; // number of calls to initializeChromosome() bool has_implicit_chromosome_; // true if the model implicitly defines a chromosome, with no initializeChromosome() call bool has_currently_initializing_chromosome_ = false; @@ -418,6 +439,13 @@ class Species : public EidosDictionaryUnretained Chromosome *GetChromosomeFromEidosValue(EidosValue *chromosome_value); // with a singleton EidosValue void GetChromosomeIndicesFromEidosValue(std::vector &chromosome_indices, EidosValue *chromosomes_value); // with a vector EidosValue + // Trait configuration and access + inline __attribute__((always_inline)) const std::vector &Traits(void) { return traits_; } + Trait *TraitFromName(const std::string &p_name); + Trait *TraitFromStringID(EidosGlobalStringID p_string_id); + void MakeImplicitTrait(void); + void AddTrait(Trait *p_trait); // takes over a retain count from the caller + // Memory usage void TabulateSLiMMemoryUsage_Species(SLiMMemoryUsage_Species *p_usage); // used by outputUsage() and SLiMgui profiling void DeleteAllMutationRuns(void); // for cleanup @@ -607,11 +635,12 @@ class Species : public EidosDictionaryUnretained inline EidosSymbolTableEntry &SymbolTableEntry(void) { return self_symbol_; }; EidosValue_SP ExecuteContextFunction_initializeAncestralNucleotides(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteContextFunction_initializeChromosome(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteContextFunction_initializeGenomicElement(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteContextFunction_initializeGenomicElementType(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteContextFunction_initializeMutationType(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteContextFunction_initializeRecombinationRate(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteContextFunction_initializeChromosome(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteContextFunction_initializeTrait(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteContextFunction_initializeGeneConversion(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteContextFunction_initializeMutationRate(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteContextFunction_initializeHotspotMap(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); @@ -637,6 +666,8 @@ class Species : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_chromosomesOfType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_chromosomesWithIDs(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_chromosomesWithSymbols(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_traitsWithIndices(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_traitsWithNames(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_individualsWithPedigreeIDs(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_killIndividuals(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_mutationFreqsCounts(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 9d1850e4..6cbc5b10 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -21,6 +21,7 @@ #include "species.h" #include "community.h" +#include "trait.h" #include "haplosome.h" #include "individual.h" #include "subpopulation.h" @@ -591,6 +592,11 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_function_name, p_arguments, p_interpreter) + // We allocate space that depends on the number of traits; so either traits must be explicitly defined + // already, or we implicitly define a single trait (the default behavior mirroring older SLiM versions) + if ((num_trait_inits_ == 0) && !has_implicit_trait_) + MakeImplicitTrait(); + // Figure out whether the mutation type is nucleotide-based bool nucleotide_based = (p_function_name == "initializeMutationTypeNuc"); @@ -1567,6 +1573,124 @@ EidosValue_SP Species::ExecuteContextFunction_initializeSpecies(const std::strin return gStaticEidosValueVOID; } +// ********************* (object$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [l$ directFitnessEffect = F]) +// +EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_function_name, p_arguments, p_interpreter) + // An implicit trait is not allowed to have already been defined. + if (has_implicit_trait_) + { + if (num_mutation_type_inits_ > 0) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() cannot be called to explicitly create a trait, because a trait has already been implicitly defined. This occurred because initializeMutationType() was called. To fix this error, call initializeTrait() first and then call initializeMutationType(), or don't call initializeTrait() at all if you do not need an explicitly defined trait." << EidosTerminate(); + + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): (internal error) initializeTrait() was called with an implicitly defined trait. However, the cause of this cannot be diagnosed, indicating an internal logic error." << EidosTerminate(); + } + + if (traits_.size() >= SLIM_MAX_TRAITS) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() cannot make a new trait because the maximum number of traits allowed per species (" << SLIM_MAX_TRAITS << ") has already been reached." << EidosTerminate(); + + // Get arguments + EidosValue *name_value = p_arguments[0].get(); + EidosValue *type_value = p_arguments[1].get(); + EidosValue *baselineOffset_value = p_arguments[2].get(); + EidosValue *individualOffsetMean_value = p_arguments[3].get(); + EidosValue *individualOffsetSD_value = p_arguments[4].get(); + EidosValue *directFitnessEffect_value = p_arguments[5].get(); + + // name + std::string name = name_value->StringAtIndex_NOCAST(0, nullptr); + bool name_problem = (name.length() == 0); + const std::vector *individual_properties = gSLiM_Individual_Class->Properties(); + + for (Trait *trait : traits_) + if (trait->Name() == name) + name_problem = true; + + for (EidosPropertySignature_CSP property_signature : *individual_properties) + if (property_signature->property_name_ == name) + name_problem = true; + + if (name_problem) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires a non-zero-length name that is unique within the species and does not conflict with any Individual property name." << EidosTerminate(); + + // type + std::string type_string = type_value->StringAtIndex_NOCAST(0, nullptr); + TraitType type; + + if ((type_string == "multiplicative") || (type_string == "mul")) + type = TraitType::kMultiplicative; + else if ((type_string == "additive") || (type_string == "add")) + type = TraitType::kAdditive; + else + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires type to be either 'multiplicative' (or 'mul'), or 'additive' ('add')." << EidosTerminate(); + + // baselineOffset + double baselineOffset; + + if (baselineOffset_value->Type() == EidosValueType::kValueNULL) + baselineOffset = (type == TraitType::kMultiplicative) ? 1.0 : 0.0; + else + baselineOffset = baselineOffset_value->FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(baselineOffset)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires baselineOffset to be a finite value (not NAN or INF)." << EidosTerminate(); + + // check that the default distribution is used or not used, in its entirety + if (individualOffsetMean_value->Type() != individualOffsetSD_value->Type()) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that both individual offset parameters be NULL (to use the default distribution) or be non-NULL (to specify a distribution)." << EidosTerminate(); + + // individualOffsetMean + double individualOffsetMean; + + if (individualOffsetMean_value->Type() == EidosValueType::kValueNULL) + individualOffsetMean = (type == TraitType::kMultiplicative) ? 1.0 : 0.0; + else + individualOffsetMean = individualOffsetMean_value->FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(individualOffsetMean)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires individualOffsetMean to be a finite value (not NAN or INF)." << EidosTerminate(); + + // individualOffsetSD + double individualOffsetSD; + + if (individualOffsetSD_value->Type() == EidosValueType::kValueNULL) + individualOffsetSD = (type == TraitType::kMultiplicative) ? 1.0 : 0.0; + else + individualOffsetSD = individualOffsetSD_value->FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(individualOffsetSD)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires individualOffsetSD to be a finite value (not NAN or INF)." << EidosTerminate(); + + // directFitnessEffect + bool directFitnessEffect = directFitnessEffect_value->LogicalAtIndex_NOCAST(0, nullptr); + + // Set up the new trait object; it gets a retain count on it from EidosDictionaryRetained::EidosDictionaryRetained() + Trait *trait = new Trait(*this, name, type, baselineOffset, individualOffsetMean, individualOffsetSD, directFitnessEffect); + EidosValue_SP result_SP = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(trait, gSLiM_Trait_Class)); + + // Add it to our registry; AddTrait() takes its retain count + AddTrait(trait); + num_trait_inits_++; + + if (SLiM_verbosity_level >= 1) + { + std::ostream &output_stream = p_interpreter.ExecutionOutputStream(); + + output_stream << "initializeTrait(name='" << name << "', type='" << type_string << "'"; + if (baselineOffset != 0.0) + output_stream << ", baselineOffset=" << baselineOffset << ""; + if (individualOffsetMean != 0.0) + output_stream << ", individualOffsetMean=" << individualOffsetMean << ""; + if (individualOffsetSD != 0.0) + output_stream << ", individualOffsetSD=" << individualOffsetSD << ""; + output_stream << ", directFitnessEffect=" << (directFitnessEffect ? "T" : "F") << ""; + output_stream << ");" << std::endl; + } + + return result_SP; +} + // TREE SEQUENCE RECORDING // ********************* (void)initializeTreeSeq([logical$ recordMutations = T], [Nif$ simplificationRatio = NULL], [Ni$ simplificationInterval = NULL], [logical$ checkCoalescence = F], [logical$ runCrosschecks = F], [logical$ retainCoalescentOnly = T], [Ns$ timeUnit = NULL]) // @@ -1770,6 +1894,16 @@ EidosValue_SP Species::GetProperty(EidosGlobalStringID p_property_id) return result_SP; } + case gID_traits: + { + EidosValue_Object *vec = new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Trait_Class); + EidosValue_SP result_SP = EidosValue_SP(vec); + + for (Trait *trait : traits_) + vec->push_object_element_RR(trait); + + return result_SP; + } case gEidosID_color: { return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(color_)); @@ -2007,6 +2141,8 @@ EidosValue_SP Species::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, co case gID_chromosomesOfType: return ExecuteMethod_chromosomesOfType(p_method_id, p_arguments, p_interpreter); case gID_chromosomesWithIDs: return ExecuteMethod_chromosomesWithIDs(p_method_id, p_arguments, p_interpreter); case gID_chromosomesWithSymbols: return ExecuteMethod_chromosomesWithSymbols(p_method_id, p_arguments, p_interpreter); + case gID_traitsWithIndices: return ExecuteMethod_traitsWithIndices(p_method_id, p_arguments, p_interpreter); + case gID_traitsWithNames: return ExecuteMethod_traitsWithNames(p_method_id, p_arguments, p_interpreter); case gID_individualsWithPedigreeIDs: return ExecuteMethod_individualsWithPedigreeIDs(p_method_id, p_arguments, p_interpreter); case gID_killIndividuals: return ExecuteMethod_killIndividuals(p_method_id, p_arguments, p_interpreter); case gID_mutationFrequencies: @@ -2610,7 +2746,7 @@ EidosValue_SP Species::ExecuteMethod_chromosomesWithIDs(EidosGlobalStringID p_me return EidosValue_SP(result); } -// ********************* – chromosomesWithSymbols(string symbols) +// ********************* – (object)chromosomesWithSymbols(string symbols) EidosValue_SP Species::ExecuteMethod_chromosomesWithSymbols(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) @@ -2637,6 +2773,61 @@ EidosValue_SP Species::ExecuteMethod_chromosomesWithSymbols(EidosGlobalStringID return EidosValue_SP(result); } +// ********************* – (object)traitsWithIndices(integer indices) +EidosValue_SP Species::ExecuteMethod_traitsWithIndices(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *indices_value = p_arguments[0].get(); + int indices_count = indices_value->Count(); + + if (indices_value == 0) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Trait_Class)); + + const int64_t *indices_data = indices_value->IntData(); + EidosValue_Object *result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Trait_Class))->reserve(indices_count); // reserve enough space for all results + + for (int indices_index = 0; indices_index < indices_count; indices_index++) + { + int64_t index = indices_data[indices_index]; + + if ((index < 0) || ((size_t)index >= traits_.size())) + EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_traitsWithIndices): out-of-range index (" << index << ") in traitsWithIndices()." << EidosTerminate(); + + Trait *trait = traits_[index]; + + result->push_object_element_no_check_RR(trait); + } + + return EidosValue_SP(result); +} + +// ********************* – (object)traitsWithNames(string names) +EidosValue_SP Species::ExecuteMethod_traitsWithNames(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *names_value = p_arguments[0].get(); + int names_count = names_value->Count(); + + if (names_count == 0) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Trait_Class)); + + const std::string *names_data = names_value->StringData(); + EidosValue_Object *result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Trait_Class))->reserve(names_count); // reserve enough space for all results + + for (int names_index = 0; names_index < names_count; names_index++) + { + const std::string &name = names_data[names_index]; + Trait *trait = TraitFromName(name); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_traitsWithNames): traitsWithNames() could not find a trait with the given name (" << name << ")." << EidosTerminate(); + + result->push_object_element_no_check_RR(trait); + } + + return EidosValue_SP(result); +} + // ********************* – (object)individualsWithPedigreeIDs(integer pedigreeIDs, [Nio subpops = NULL]) EidosValue_SP Species::ExecuteMethod_individualsWithPedigreeIDs(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -4276,6 +4467,7 @@ const std::vector *Species_Class::Properties(void) c properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_substitutions, true, kEidosValueMaskObject, gSLiM_Substitution_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_cycle, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_traits, true, kEidosValueMaskObject, gSLiM_Trait_Class))); std::sort(properties->begin(), properties->end(), CompareEidosPropertySignatures); } @@ -4325,6 +4517,8 @@ const std::vector *Species_Class::Methods(void) const methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_skipTick, kEidosValueMaskVOID))); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_subsetMutations, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddObject_OSN("exclude", gSLiM_Mutation_Class, gStaticEidosValueNULL)->AddIntObject_OSN("mutType", gSLiM_MutationType_Class, gStaticEidosValueNULL)->AddInt_OSN("position", gStaticEidosValueNULL)->AddIntString_OSN("nucleotide", gStaticEidosValueNULL)->AddInt_OSN("tag", gStaticEidosValueNULL)->AddInt_OSN("id", gStaticEidosValueNULL)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, "chromosome", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_substitutionsOfType, kEidosValueMaskObject, gSLiM_Substitution_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_traitsWithIndices, kEidosValueMaskObject, gSLiM_Trait_Class))->AddInt("indices")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_traitsWithNames, kEidosValueMaskObject, gSLiM_Trait_Class))->AddString("names")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_treeSeqCoalesced, kEidosValueMaskLogical | kEidosValueMaskSingleton))); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_treeSeqSimplify, kEidosValueMaskVOID))); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_treeSeqRememberIndividuals, kEidosValueMaskVOID))->AddObject("individuals", gSLiM_Individual_Class)->AddLogical_OS("permanent", gStaticEidosValue_LogicalT)); diff --git a/core/trait.cpp b/core/trait.cpp new file mode 100644 index 00000000..bc312bfb --- /dev/null +++ b/core/trait.cpp @@ -0,0 +1,254 @@ +// +// trait.cpp +// SLiM +// +// Created by Ben Haller on 6/25/25. +// Copyright © 2025 Messer Lab, http://messerlab.org/software/. All rights reserved. +// + +#include "trait.h" +#include "community.h" +#include "species.h" + + +Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, double p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect) : + index_(-1), name_(p_name), type_(p_type), baselineOffset_(p_baselineOffset), + individualOffsetMean_(p_individualOffsetMean), individualOffsetSD_(p_individualOffsetSD), + directFitnessEffect_(p_directFitnessEffect), community_(p_species.community_), species_(p_species) +{ +} + +Trait::~Trait(void) +{ + //EIDOS_ERRSTREAM << "Trait::~Trait" << std::endl; +} + +const EidosClass *Trait::Class(void) const +{ + return gSLiM_Trait_Class; +} + +void Trait::Print(std::ostream &p_ostream) const +{ + p_ostream << Class()->ClassNameForDisplay() << "<" << name_ << ">"; +} + +EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) +{ + // All of our strings are in the global registry, so we can require a successful lookup + switch (p_property_id) + { + // constants + case gID_index: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(index_)); + } + case gID_name: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(name_)); + } + case gID_species: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(&species_, gSLiM_Species_Class)); + } + case gEidosID_type: + { + static EidosValue_SP static_type_string_multiplicative; + static EidosValue_SP static_type_string_additive; + +#pragma omp critical (GetProperty_trait_type) + { + if (!static_type_string_multiplicative) + { + static_type_string_multiplicative = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("multiplicative")); + static_type_string_additive = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("additive")); + } + } + + switch (type_) + { + case TraitType::kMultiplicative: return static_type_string_multiplicative; + case TraitType::kAdditive: return static_type_string_additive; + default: return gStaticEidosValueNULL; // never hit; here to make the compiler happy + } + } + + // variables + case gID_baselineOffset: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(baselineOffset_)); + } + case gID_directFitnessEffect: + { + return (directFitnessEffect_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + } + case gID_individualOffsetMean: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(individualOffsetMean_)); + } + case gID_individualOffsetSD: + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(individualOffsetSD_)); + } + case gID_tag: + { + slim_usertag_t tag_value = tag_value_; + + if (tag_value == SLIM_TAG_UNSET_VALUE) + EIDOS_TERMINATION << "ERROR (Trait::GetProperty): property tag accessed on trait before being set." << EidosTerminate(); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(tag_value)); + } + + // all others, including gID_none + default: + return super::GetProperty(p_property_id); + } +} + +void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) +{ + // All of our strings are in the global registry, so we can require a successful lookup + switch (p_property_id) + { + case gID_baselineOffset: + { + double value = p_value.FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(value)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property baselineOffset requires a finite value (not NAN or INF)." << EidosTerminate(); + + baselineOffset_ = value; + return; + } + case gID_directFitnessEffect: + { + bool value = p_value.LogicalAtIndex_NOCAST(0, nullptr); + + directFitnessEffect_ = value; + return; + } + case gID_individualOffsetMean: + { + double value = p_value.FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(value)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property individualOffsetMean requires a finite value (not NAN or INF)." << EidosTerminate(); + + individualOffsetMean_ = value; + return; + } + case gID_individualOffsetSD: + { + double value = p_value.FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(value)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property individualOffsetSD requires a finite value (not NAN or INF)." << EidosTerminate(); + + individualOffsetSD_ = value; + return; + } + case gID_tag: + { + slim_usertag_t value = SLiMCastToUsertagTypeOrRaise(p_value.IntAtIndex_NOCAST(0, nullptr)); + + tag_value_ = value; + return; + } + + // all others, including gID_none + default: + return super::SetProperty(p_property_id, p_value); + } +} + +EidosValue_SP Trait::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ + switch (p_method_id) + { + default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); + } +} + + +// +// Trait_Class +// +#pragma mark - +#pragma mark Trait_Class +#pragma mark - + +EidosClass *gSLiM_Trait_Class = nullptr; + +const std::vector *Trait_Class::Properties(void) const +{ + static std::vector *properties = nullptr; + + if (!properties) + { + THREAD_SAFETY_IN_ANY_PARALLEL("Trait_Class::Properties(): not warmed up"); + + properties = new std::vector(*super::Properties()); + + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_baselineOffset, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_directFitnessEffect, false, kEidosValueMaskLogical | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_index, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_individualOffsetMean, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_individualOffsetSD, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_name, true, kEidosValueMaskString | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_species, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Species_Class))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_type, true, kEidosValueMaskString | kEidosValueMaskSingleton))); + + std::sort(properties->begin(), properties->end(), CompareEidosPropertySignatures); + } + + return properties; +} + +const std::vector *Trait_Class::Methods(void) const +{ + static std::vector *methods = nullptr; + + if (!methods) + { + THREAD_SAFETY_IN_ANY_PARALLEL("Trait_Class::Methods(): not warmed up"); + + methods = new std::vector(*super::Methods()); + + + std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); + } + + return methods; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/trait.h b/core/trait.h new file mode 100644 index 00000000..d4fc06cb --- /dev/null +++ b/core/trait.h @@ -0,0 +1,138 @@ +// +// trait.h +// SLiM +// +// Created by Ben Haller on 6/25/25. +// Copyright (c) 2025 Benjamin C. Haller. All rights reserved. +// A product of the Messer Lab, http://messerlab.org/slim/ +// + +// This file is part of SLiM. +// +// SLiM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// SLiM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with SLiM. If not, see . + +/* + + The class Trait represents a phenotypic trait. More than one trait can be defined for a given species, and mutations can + influence the value of more than one trait. Traits can be multiplicative (typically a population genetics style of trait) + or additive (typically a quantitative genetics style of trait). + + */ + +#ifndef __SLiM__trait__ +#define __SLiM__trait__ + + +class Community; +class Species; + +#include "eidos_globals.h" +#include "slim_globals.h" +#include "eidos_class_Dictionary.h" + + +extern EidosClass *gSLiM_Trait_Class; + + +class Trait : public EidosDictionaryRetained +{ + // This class has its copy constructor and assignment operator disabled, to prevent accidental copying. + +private: + typedef EidosDictionaryRetained super; + +#ifdef SLIMGUI +public: +#else +private: +#endif + + int64_t index_; // the index of this trait within its species + std::string name_; // the user-visible name of this trait + TraitType type_; // multiplicative or additive + + // offsets + double baselineOffset_; + double individualOffsetMean_; + double individualOffsetSD_; + + // if true, the calculated trait value is used directly as a fitness effect, automatically + // this mimics the previous behavior of SLiM, for multiplicative traits + bool directFitnessEffect_; + +public: + + Community &community_; + Species &species_; + + // a user-defined tag value + slim_usertag_t tag_value_ = SLIM_TAG_UNSET_VALUE; + + Trait(const Trait&) = delete; // no copying + Trait& operator=(const Trait&) = delete; // no copying + Trait(void) = delete; // no null constructor + + explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, double p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); + ~Trait(void); + + inline __attribute__((always_inline)) int64_t Index(void) const { return index_; } + inline __attribute__((always_inline)) void SetIndex(int64_t p_index) { index_ = p_index; } // only from AddTrait() + inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } + + + // + // Eidos support + // + virtual const EidosClass *Class(void) const override; + virtual void Print(std::ostream &p_ostream) const override; + + virtual EidosValue_SP GetProperty(EidosGlobalStringID p_property_id) override; + virtual void SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) override; + + virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; +}; + +class Trait_Class : public EidosDictionaryRetained_Class +{ +private: + typedef EidosDictionaryRetained_Class super; + +public: + Trait_Class(const Trait_Class &p_original) = delete; // no copy-construct + Trait_Class& operator=(const Trait_Class&) = delete; // no copying + inline Trait_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } + + virtual const std::vector *Properties(void) const override; + virtual const std::vector *Methods(void) const override; +}; + + +#endif /* defined(__SLiM__trait__) */ + + + + + + + + + + + + + + + + + + + + + + diff --git a/eidos/eidos_class_Object.cpp b/eidos/eidos_class_Object.cpp index 6f46b6b3..da521dde 100644 --- a/eidos/eidos_class_Object.cpp +++ b/eidos/eidos_class_Object.cpp @@ -90,7 +90,7 @@ nlohmann::json EidosObject::JSONRepresentation(void) const EidosValue_SP EidosObject::GetProperty(EidosGlobalStringID p_property_id) { // This is the backstop, called by subclasses - EIDOS_TERMINATION << "ERROR (EidosObject::GetProperty for " << Class()->ClassNameForDisplay() << "): attempt to get a value for property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " was not handled by subclass." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (EidosObject::GetProperty): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << Class()->ClassNameForDisplay() << "." << EidosTerminate(nullptr); } void EidosObject::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) @@ -470,7 +470,7 @@ bool EidosClass::IsSubclassOfClass(const EidosClass *p_class_object) const void EidosClass::CacheDispatchTables(void) { - // This can be called more than once during startup, because Eidos warms up and the SLiM warms up + // This can be called more than once during startup, because Eidos warms up and then SLiM warms up if (dispatches_cached_) return; @@ -506,7 +506,7 @@ void EidosClass::CacheDispatchTables(void) method_signatures_dispatch_capacity_ = last_id + 1; // this limit may need to be lifted someday, but for now it's a sanity check if the uniquing code changes - if (method_signatures_dispatch_capacity_ > 512) + if (method_signatures_dispatch_capacity_ > 600) EIDOS_TERMINATION << "ERROR (EidosClass::CacheDispatchTables): (internal error) method dispatch table unreasonably large for class " << ClassName() << "." << EidosTerminate(nullptr); method_signatures_dispatch_ = (EidosMethodSignature_CSP *)calloc(method_signatures_dispatch_capacity_, sizeof(EidosMethodSignature_CSP)); @@ -709,6 +709,19 @@ EidosValue_SP EidosClass::ExecuteMethod_size_length(EidosGlobalStringID p_method return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(p_target->Count())); } +EidosValue_SP EidosClass::GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) const +{ + // This is the backstop, called by subclasses + EIDOS_TERMINATION << "ERROR (EidosObject::GetProperty_NO_SIGNATURE): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ClassNameForDisplay() << "." << EidosTerminate(nullptr); +} + +void EidosClass::SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_value) const +{ + // This is the backstop, called by subclasses + EIDOS_TERMINATION << "ERROR (EidosObject::SetProperty_NO_SIGNATURE): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ClassNameForDisplay() << "." << EidosTerminate(nullptr); +} + + diff --git a/eidos/eidos_class_Object.h b/eidos/eidos_class_Object.h index dbf29671..4fe87e2c 100644 --- a/eidos/eidos_class_Object.h +++ b/eidos/eidos_class_Object.h @@ -206,6 +206,9 @@ class EidosClass EidosValue_SP ExecuteMethod_propertySignature(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_methodSignature(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_size_length(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + + virtual EidosValue_SP GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size) const; + virtual void SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size, const EidosValue &p_value) const; }; diff --git a/eidos/eidos_globals.h b/eidos/eidos_globals.h index e65eece3..31fef121 100644 --- a/eidos/eidos_globals.h +++ b/eidos/eidos_globals.h @@ -1219,7 +1219,7 @@ enum _EidosGlobalStringID : uint32_t gEidosID_Individual, gEidosID_LastEntry, // IDs added by the Context should start here - gEidosID_LastContextEntry = 535 // IDs added by the Context must end before this value; Eidos reserves the remaining values + gEidosID_LastContextEntry = 545 // IDs added by the Context must end before this value; Eidos reserves the remaining values }; extern std::vector gEidosConstantNames; // T, F, NULL, PI, E, INF, NAN diff --git a/eidos/eidos_test_functions_vector.cpp b/eidos/eidos_test_functions_vector.cpp index 9cbfa5a7..09bfa655 100644 --- a/eidos/eidos_test_functions_vector.cpp +++ b/eidos/eidos_test_functions_vector.cpp @@ -992,7 +992,7 @@ void _RunFunctionValueInspectionManipulationTests_s_through_z(void) EidosAssertScriptSuccess_IV("sortBy(c(_Test(7), _Test(2), _Test(-8), _Test(3), _Test(75)), '_yolk')._yolk;", {-8, 2, 3, 7, 75}); EidosAssertScriptSuccess_IV("sortBy(c(_Test(7), _Test(2), _Test(-8), _Test(3), _Test(75)), '_yolk', T)._yolk;", {-8, 2, 3, 7, 75}); EidosAssertScriptSuccess_IV("sortBy(c(_Test(7), _Test(2), _Test(-8), _Test(3), _Test(75)), '_yolk', F)._yolk;", {75, 7, 3, 2, -8}); - EidosAssertScriptRaise("sortBy(c(_Test(7), _Test(2), _Test(-8), _Test(3), _Test(75)), '_foo')._yolk;", 0, "attempt to get a value"); + EidosAssertScriptRaise("sortBy(c(_Test(7), _Test(2), _Test(-8), _Test(3), _Test(75)), '_foo')._yolk;", 0, "property _foo is not defined"); // str() – can't test the actual output, but we can make sure it executes... EidosAssertScriptSuccess_VOID("str(NULL);"); diff --git a/eidos/eidos_value.cpp b/eidos/eidos_value.cpp index 205a5951..363b4973 100644 --- a/eidos/eidos_value.cpp +++ b/eidos/eidos_value.cpp @@ -2149,8 +2149,24 @@ EidosValue_SP EidosValue_Object::GetPropertyOfElements(EidosGlobalStringID p_pro size_t values_size = count_; const EidosPropertySignature *signature = class_->SignatureForProperty(p_property_id); + // BCH 6/29/2025: To enable the special trait properties of Individual, we now allow + // property access to occur without a signature, and thus without type checks. This + // goes through a special vectorized method, not through GetProperty()! if (!signature) - EIDOS_TERMINATION << "ERROR (EidosValue_Object::GetPropertyOfElements): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ElementType() << "." << EidosTerminate(nullptr); + { + const EidosClass *target_class = class_; + + if (values_size == 0) + EIDOS_TERMINATION << "ERROR (EidosValue_Object::GetPropertyOfElements): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " does not specify an unambiguous value type, and thus cannot be accessed on a zero-length vector." << EidosTerminate(nullptr); + + EidosValue_SP result = target_class->GetProperty_NO_SIGNATURE(p_property_id, values_, values_size); + + // Access of singleton properties retains the matrix/array structure of the target + if (signature->value_mask_ & kEidosValueMaskSingleton) + result->CopyDimensionsFromValue(this); + + return result; + } if (values_size == 0) { @@ -2280,12 +2296,28 @@ EidosValue_SP EidosValue_Object::GetPropertyOfElements(EidosGlobalStringID p_pro void EidosValue_Object::SetPropertyOfElements(EidosGlobalStringID p_property_id, const EidosValue &p_value, EidosToken *p_property_token) { - const EidosPropertySignature *signature = Class()->SignatureForProperty(p_property_id); + const EidosPropertySignature *signature = class_->SignatureForProperty(p_property_id); - // BCH 9 Sept. 2022: if the property does not exist, raise an error on the token for the property name. - // Note that other errors stemming from this call will refer to whatever the current error range is. + // BCH 6/29/2025: To enable the special trait properties of Individual, we now allow + // property access to occur without a signature, and thus without type checks. This + // goes through a special vectorized method, not through SetProperty()! if (!signature) - EIDOS_TERMINATION << "ERROR (EidosValue_Object::SetPropertyOfElements): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ElementType() << "." << EidosTerminate(p_property_token); + { + // We have to check the count ourselves; the signature does not do that for us + const EidosClass *target_class = class_; + size_t p_value_count = p_value.Count(); + size_t values_size = count_; + + // we have a multiplex assignment of one value to (maybe) more than one element: x.foo = 10 + // OR, we have a one-to-one assignment of values to elements: x.foo = 1:5 (where x has 5 elements) + if ((p_value_count == 1) || (p_value_count == values_size)) + { + if (p_value_count) + target_class->SetProperty_NO_SIGNATURE(p_property_id, values_, values_size, p_value); + } + else + EIDOS_TERMINATION << "ERROR (EidosValue_Object::SetPropertyOfElements): assignment to a property requires an rvalue that is a singleton (multiplex assignment) or that has a .size() matching the .size of the lvalue." << EidosTerminate(nullptr); + } signature->CheckAssignedValue(p_value); // will raise if the type being assigned in is not an exact match From 35c511ba2a2c2309f8fa1934505197427709ff42 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 2 Jul 2025 20:33:59 +0200 Subject: [PATCH 002/107] add dominanceCoeff to Mutation, and related work --- QtSLiM/QtSLiMTablesDrawer.cpp | 2 +- QtSLiM/help/SLiMHelpClasses.html | 70 ++++++++-------- SLiMgui/SLiMHelpClasses.rtf | 134 +++++++++++++++++++++---------- SLiMgui/SLiMWindowController.mm | 2 +- VERSIONS | 3 + core/chromosome.cpp | 8 +- core/core.pro | 6 +- core/haplosome.cpp | 33 ++++---- core/individual.cpp | 25 +++--- core/mutation.cpp | 54 +++++++++++-- core/mutation.h | 10 ++- core/mutation_type.cpp | 18 +++-- core/mutation_type.h | 2 +- core/polymorphism.cpp | 8 +- core/population.cpp | 6 +- core/slim_globals.cpp | 1 + core/slim_globals.h | 2 + core/species.cpp | 27 +++---- core/species.h | 1 + core/substitution.cpp | 27 +++++-- core/substitution.h | 6 +- eidos/eidos_class_Object.cpp | 8 +- 22 files changed, 293 insertions(+), 160 deletions(-) diff --git a/QtSLiM/QtSLiMTablesDrawer.cpp b/QtSLiM/QtSLiMTablesDrawer.cpp index ba143981..12c8a502 100644 --- a/QtSLiM/QtSLiMTablesDrawer.cpp +++ b/QtSLiM/QtSLiMTablesDrawer.cpp @@ -552,7 +552,7 @@ QVariant QtSLiMMutTypeTableModel::data(const QModelIndex &p_index, int role) con } else if (p_index.column() == 1) { - return QVariant(QString("%1").arg(static_cast(mutationType->dominance_coeff_), 0, 'f', 3)); + return QVariant(QString("%1").arg(static_cast(mutationType->default_dominance_coeff_), 0, 'f', 3)); } else if (p_index.column() == 2) { diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index ba83b039..03ee71cc 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -37,12 +37,11 @@ span.s12 {text-decoration: underline ; color: #0000ff} span.s13 {font: 6.7px 'Times New Roman'} span.s14 {font: 10.0px 'Lucida Grande'} - span.s15 {font: 10.0px 'Times New Roman'; color: #000000} - span.s16 {font: 11.0px 'Times New Roman'} - span.s17 {font: 11.0px Helvetica} - span.s18 {font: 6.7px Optima} - span.s19 {font: 10.0px Optima; color: #000000} - span.s20 {font: 6.7px Optima; font-kerning: none} + span.s15 {font: 11.0px 'Times New Roman'} + span.s16 {font: 11.0px Helvetica} + span.s17 {font: 6.7px Optima} + span.s18 {font: 10.0px Optima; color: #000000} + span.s19 {font: 6.7px Optima; font-kerning: none} span.Apple-tab-span {white-space:pre} @@ -668,6 +667,9 @@

5.10.1  Mutation properties

chromosome => (object<Chromosome>$)

The Chromosome object with which the mutation is associated.

+

dominanceCoeff => (float$)

+

The dominance coefficient of the mutation, taken from the default dominance coefficient of its MutationType.  If a mutation has a selectionCoeff of s and a dominanceCoeff of h, the multiplicative fitness effect of the mutation in a homozygote is 1+s, and in a heterozygote is 1+hs.  The dominance coefficient of a mutation can be changed with the setDominanceCoeff() method.

+

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s dominance coefficient to some number x, mut.dominanceCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutations.

id => (integer$)

The identifier for this mutation.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run.  These identifiers are not re-used during a run, except that if a population file is loaded from disk, the loaded mutations will receive their original identifier values as saved in the population file.

isFixed => (logical$)

@@ -684,22 +686,24 @@

The tick in which this mutation arose.

position => (integer$)

The position in the chromosome of this mutation.

-

selectionCoeff => (float$)

-

The selection coefficient of the mutation, drawn from the distribution of fitness effects of its MutationType.  If a mutation has a selectionCoeff of s, the multiplicative fitness effect of the mutation in a homozygote is 1+s; in a heterozygote it is 1+hs, where h is the dominance coefficient kept by the mutation type.

-

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s selection coefficient to some number x, mut.selectionCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutations.

+

selectionCoeff => (float$)

+

The selection coefficient of the mutation, drawn from the distribution of fitness effects of its MutationType.  If a mutation has a selectionCoeff of s and a dominanceCoeff of h, the multiplicative fitness effect of the mutation in a homozygote is 1+s, and in a heterozygote is 1+hs.  The selection coefficient of a mutation can be changed with the setSelectionCoeff() method.

+

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s selection coefficient to some number x, mut.selectionCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutations.

subpopID <–> (integer$)

The identifier of the subpopulation in which this mutation arose.  This property can be used to track the ancestry of mutations through their subpopulation of origin.

If you don’t care which subpopulation a mutation originated in, the subpopID may be used as an arbitrary integer “tag” value for any purpose you wish; SLiM does not do anything with the value of subpopID except propagate it to Substitution objects and report it in output.  (It must still be >= 0, however, since SLiM object identifiers are limited to nonnegative integers).

tag <–> (integer$)

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.

5.10.2  Mutation methods

+

– (void)setDominanceCoeff(float$ dominanceCoeff)

+

Set the dominance coefficient of the mutation to dominanceCoeff.  The dominance coefficient will be changed for all individuals that possess the mutation, since they all share a single Mutation object (note that the selection coefficient will remain unchanged).

+

Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

– (void)setMutationType(io<MutationType>$ mutType)

-

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  This implicitly changes the dominance coefficient of the mutation to that of the new mutation type, since the dominance coefficient is a property of the mutation type.  On the other hand, the selection coefficient of the mutation is not changed, since it is a property of the mutation object itself; it can be changed explicitly using the setSelectionCoeff() method if so desired.

-

The mutation type of a mutation is normally a constant in simulations, so be sure you know what you are doing.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

+

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  The selection coefficients and dominance coefficients of existing mutations are not changed, since they are properties of the mutation objects themselves; they can be changed explicitly using the setSelectionCoeff() and setDominanceCoeff() methods of Mutation if so desired.

In nucleotide-based models, a restriction applies: nucleotide-based mutations may not be changed to a non-nucleotide-based mutation type, and non-nucleotide-based mutations may not be changed to a nucleotide-based mutation type.

-

– (void)setSelectionCoeff(float$ selectionCoeff)

-

Set the selection coefficient of the mutation to selectionCoeff.  The selection coefficient will be changed for all individuals that possess the mutation, since they all share a single Mutation object (note that the dominance coefficient will remain unchanged, as it is determined by the mutation type).

-

This is normally a constant in simulations, so be sure you know what you are doing; often setting up a mutationEffect() callback is preferable, in order to modify the selection coefficient in a more limited and controlled fashion.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

+

– (void)setSelectionCoeff(float$ selectionCoeff)

+

Set the selection coefficient of the mutation to selectionCoeff.  The selection coefficient will be changed for all individuals that possess the mutation, since they all share a single Mutation object (note that the dominance coefficient will remain unchanged).

+

Often setting up a mutationEffect() callback is preferable, in order to modify the selection coefficient in a more limited and controlled fashion.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

5.11  Class MutationType

5.11.1  MutationType properties

color <–> (string$)

@@ -716,19 +720,19 @@

distributionType => (string$)

The type of distribution of fitness effects; one of "f", "g", "e", "n", "w", or "s":

"f" – A fixed fitness effect.  This DFE type has a single parameter, the selection coefficient s to be used by all mutations of the mutation type.

-

"g" – A gamma-distributed fitness effect.  This DFE type is specified by two parameters, a shape parameter and a mean value.  The gamma distribution from which mutations are drawn is given by the probability density function P(s | α,β= [Γ(α)βα]−1exp(−s/β), where α is the shape parameter, and the specified mean for the distribution is equal to αβ.  Note that this parameterization is the same as for the Eidos function rgamma().  A gamma distribution is often used to model deleterious mutations at functional sites.

-

"e" – An exponentially-distributed fitness effect.  This DFE type is specified by a single parameter, the mean of the distribution.  The exponential distribution from which mutations are drawn is given by the probability density function P(s | β) = β−1exp(−s/β), where β is the specified mean for the distribution.  This parameterization is the same as for the Eidos function rexp().  An exponential distribution is often used to model beneficial mutations.

-

"n" – A normally-distributed fitness effect.  This DFE type is specified by two parameters, a mean and a standard deviation.  The normal distribution from which mutations are drawn is given by the probability density function P(s | μ,σ) = (2πσ2)−1/2exp(−(sμ)2/2σ2), where μ is the mean and σ is the standard deviation.  This parameterization is the same as for the Eidos function rnorm().  A normal distribution is often used to model mutations that can be either beneficial or deleterious, since both tails of the distribution are unbounded.

-

"p" – A Laplace-distributed fitness effect.  This DFE type is specified by two parameters, a mean and a scale.  The Laplace distribution from which mutations are drawn is given by the probability density function P(s | μ,b) = exp(−|sμ|/b)/2b, where μ is the mean and b is the scale parameter.  A Laplace distribution is sometimes used to model a mix of both deleterious and beneficial mutations.

-

"w" – A Weibull-distributed fitness effect.  This DFE type is specified by a scale parameter and a shape parameter.  The Weibull distribution from which mutations are drawn is given by the probability density function P(s | λ,k) = (k/λk)sk−1exp(−(s/λ)k), where λ is the scale parameter and k is the shape parameter.  This parameterization is the same as for the Eidos function rweibull().  A Weibull distribution is often used to model mutations following extreme-value theory.

+

"g" – A gamma-distributed fitness effect.  This DFE type is specified by two parameters, a shape parameter and a mean value.  The gamma distribution from which mutations are drawn is given by the probability density function P(s | α,β= [Γ(α)βα]−1exp(−s/β), where α is the shape parameter, and the specified mean for the distribution is equal to αβ.  Note that this parameterization is the same as for the Eidos function rgamma().  A gamma distribution is often used to model deleterious mutations at functional sites.

+

"e" – An exponentially-distributed fitness effect.  This DFE type is specified by a single parameter, the mean of the distribution.  The exponential distribution from which mutations are drawn is given by the probability density function P(s | β) = β−1exp(−s/β), where β is the specified mean for the distribution.  This parameterization is the same as for the Eidos function rexp().  An exponential distribution is often used to model beneficial mutations.

+

"n" – A normally-distributed fitness effect.  This DFE type is specified by two parameters, a mean and a standard deviation.  The normal distribution from which mutations are drawn is given by the probability density function P(s | μ,σ) = (2πσ2)−1/2exp(−(sμ)2/2σ2), where μ is the mean and σ is the standard deviation.  This parameterization is the same as for the Eidos function rnorm().  A normal distribution is often used to model mutations that can be either beneficial or deleterious, since both tails of the distribution are unbounded.

+

"p" – A Laplace-distributed fitness effect.  This DFE type is specified by two parameters, a mean and a scale.  The Laplace distribution from which mutations are drawn is given by the probability density function P(s | μ,b) = exp(−|sμ|/b)/2b, where μ is the mean and b is the scale parameter.  A Laplace distribution is sometimes used to model a mix of both deleterious and beneficial mutations.

+

"w" – A Weibull-distributed fitness effect.  This DFE type is specified by a scale parameter and a shape parameter.  The Weibull distribution from which mutations are drawn is given by the probability density function P(s | λ,k) = (k/λk)sk−1exp(−(s/λ)k), where λ is the scale parameter and k is the shape parameter.  This parameterization is the same as for the Eidos function rweibull().  A Weibull distribution is often used to model mutations following extreme-value theory.

"s" – A script-based fitness effect.  This DFE type is specified by a script parameter of type string, specifying an Eidos script to be executed to produce each new selection coefficient.  For example, the script "return rbinom(1);" could be used to generate selection coefficients drawn from a binomial distribution, using the Eidos function rbinom(), even though that mutational distribution is not supported by SLiM directly.  The script must return a singleton float or integer.

-

Note that these distributions can in principle produce selection coefficients smaller than -1.0. In that case, the mutations will be evaluated as “lethal” by SLiM, and the relative fitness of the individual will be set to 0.0.

-

dominanceCoeff <–> (float$)

-

The dominance coefficient used for mutations of this type when heterozygous.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

-

Note that the dominance coefficient is not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

-

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation type muttype’s dominance coefficient to some number x, muttype.dominanceCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutation types.

+

Note that these distributions can in principle produce selection coefficients smaller than -1.0. In that case, the mutations will be evaluated as “lethal” by SLiM, and the relative fitness of the individual will be set to 0.0.

+

dominanceCoeff <–> (float$)

+

The default dominance coefficient used for mutations of this type when heterozygous.  This default value is taken by new mutations of this mutation type when they are created, as the value of their dominanceCoeff property, but that can be changed later with the Mutation method setDominanceCoeff().

+

Note that the dominance coefficient is not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

+

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation type muttype’s dominance coefficient to some number x, muttype.dominanceCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutation types.

hemizygousDominanceCoeff <–> (float$)

-

The dominance coefficient used for mutations of this type when they occur opposite a null haplosome (as can occur in sex-chromosome models and models involving a mix of haploids and diploids).  This defaults to 1.0, and is used only in models where null haplosomes are present; the dominanceCoeff property is the dominance coefficient used in most circumstances.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

+

The dominance coefficient used for mutations of this type when they occur opposite a null haplosome (as can occur in sex-chromosome models and models involving a mix of haploids and diploids).  This defaults to 1.0, and is used only in models where null haplosomes are present; the dominanceCoeff property of the mutation is the dominance coefficient used in most circumstances.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

As with the dominanceCoeff property, this is stored internally using a single-precision float; see the documentation for dominanceCoeff for discussion.

id => (integer$)

The identifier for this mutation type; for mutation type m3, for example, this is 3.

@@ -857,7 +861,7 @@

– (object<SpatialMap>$)divide(ifo<SpatialMap> x)

Divides the spatial map by x.  One possibility is that x is a singleton integer or float value; in this case, each grid value of the target spatial map is divided by x.  Another possibility is that x is an integer or float vector/matrix/array of the same dimensions as the target spatial map’s grid; in this case, each grid value of the target spatial map is divided by the corresponding value of x.  The third possibility is that x is itself a (singleton) spatial map; in this case, each grid value of the target spatial map is divided by the corresponding grid value of x (and thus the two spatial maps must match in their spatiality, their spatial bounds, and their grid dimensions).  The target spatial map is returned, to allow easy chaining of operations.

– (object<SpatialMap>$)exp(void)

-

Exponentiates the values of the spatial map.  More precisely, each grid value x of the target spatial map is exponentiated – replaced by the value ex.  The target spatial map is returned, to allow easy chaining of operations.

+

Exponentiates the values of the spatial map.  More precisely, each grid value x of the target spatial map is exponentiated – replaced by the value ex.  The target spatial map is returned, to allow easy chaining of operations.

– (float)gridValues(void)

Returns the values for the spatial map’s grid as a vector (for a 1D map), a matrix (for a 2D map), or an array (for a 3D map).  The form and orientation of the returned values is such that it could be used to create a new spatial map, with defineSpatialMap(), which would be identical to the original.

– (object<SpatialMap>$)interpolate(integer$ factor, [string$ method = "linear"])

@@ -990,10 +994,10 @@

Note that this method is only for use in nonWF models, in which mortality is managed manually by the model script.  In WF models, mortality is managed automatically by the SLiM core when the new offspring generation becomes the parental generation and the previous parental generation dies; mortality does not otherwise occur in WF models.  In nonWF models, mortality normally occurs during the survival stage of the tick cycle, based upon the fitness values calculated by SLiM, and survival() callbacks can influence the outcome of that survival stage.  Calls to killIndividuals(), on the other hand, can be made at any time during first(), early(), or late() events, and the result cannot be modified by survival() callbacks; the given individuals are simply immediately killed.  This method therefore provides an alternative, and relatively rarely used, mortality mechanism that is disconnected from fitness.

– (integer)mutationCounts(Nio<Subpopulation> subpops, [No<Mutation> mutations = NULL])

Return an integer vector with the frequency counts of all of the Mutation objects passed in mutations, within the Subpopulation objects in subpops.  The subpops argument is required, but you may pass NULL to get population-wide frequency counts.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects.  If the optional mutations argument is NULL (the default), frequency counts will be returned for all of the active Mutation objects in the species – the same Mutation objects, and in the same order, as would be returned by the mutations property of sim, in other words.

-

See the -mutationFrequencies() method to obtain float frequencies instead of integer counts.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

+

See the -mutationFrequencies() method to obtain float frequencies instead of integer counts.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

– (float)mutationFrequencies(Nio<Subpopulation> subpops, [No<Mutation> mutations = NULL])

Return a float vector with the frequencies of all of the Mutation objects passed in mutations, within the Subpopulation objects in subpops.  The subpops argument is required, but you may pass NULL to get population-wide frequencies.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects.  If the optional mutations argument is NULL (the default), frequencies will be returned for all of the active Mutation objects in the species – the same Mutation objects, and in the same order, as would be returned by the mutations property of sim, in other words.

-

See the -mutationCounts() method to obtain integer counts instead of float frequencies.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

+

See the -mutationCounts() method to obtain integer counts instead of float frequencies.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

 (object<Mutation>)mutationsOfType(io<MutationType>$ mutType)

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations that are currently active in the species.  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType().  This method is often used to look up an introduced mutation at a later point in the simulation, since there is no way to keep persistent references to objects in SLiM.  This method is provided for speed; it is much faster than the corresponding Eidos code.

– (void)outputFixedMutations([Ns$ filePath = NULL], [logical$ append = F], [logical$ objectTags = F])

@@ -1006,7 +1010,7 @@

Output the state of the entire population.  If the optional parameter filePath is NULL (the default), output will be sent to Eidos’s output stream.  Otherwise, output will be sent to the filesystem path specified by filePath, overwriting that file if append if F, or appending to the end of it if append is T.  When writing to a file, a logical flag, binary, may be supplied as well.  If binary is T, the population state will be written as a binary file instead of a text file (binary data cannot be written to the standard output stream).  The binary file is usually smaller, and in any case will be read much faster than the corresponding text file would be read.  Binary files are not guaranteed to be portable between platforms; in other words, a binary file written on one machine may not be readable on a different machine (but in practice it usually will be, unless the platforms being used are fairly unusual).  If binary is F (the default), a text file will be written.

Beginning with SLiM 2.3, the spatialPositions parameter may be used to control the output of the spatial positions of individuals in species for which continuous space has been enabled using the dimensionality option of initializeSLiMOptions().  If spatialPositions is F, the output will not contain spatial positions, and will be identical to the output generated by SLiM 2.1 and later.  If spatialPositions is T, spatial position information will be output if it is available.  If the species does not have continuous space enabled, the spatialPositions parameter will be ignored.  Positional information may be output for all output destinations – the Eidos output stream, a text file, or a binary file.

Beginning with SLiM 3.0, the ages parameter may be used to control the output of the ages of individuals in nonWF simulations.  If ages is F, the output will not contain ages, preserving backward compatibility with the output format of SLiM 2.1 and later.  If ages is T, ages will be output for nonWF models.  In WF simulations, the ages parameter will be ignored.

-

Beginning with SLiM 3.3, the ancestralNucleotides parameter may be used to control the output of the ancestral nucleotide sequence in nucleotide-based models.  If ancestralNucleotides is F, the output will not contain ancestral nucleotide information, and so the ancestral sequence will not be restored correctly if the saved file is loaded with readPopulationFile().  This option is provided because the ancestral sequence may be quite large, for models with a long chromosome (e.g., 1 GB if the chromosome is 109 bases long, when saved in text format, or 0.25 GB when saved in binary format).  If the model is not nucleotide-based (as enabled with the nucleotideBased parameter to initializeSLiMOptions()), the ancestralNucleotides parameter will be ignored.  Note that in nucleotide-based models the output format will always include the nucleotides associated with any nucleotide-based mutations; the ancestralNucleotides flag governs only the ancestral sequence.

+

Beginning with SLiM 3.3, the ancestralNucleotides parameter may be used to control the output of the ancestral nucleotide sequence in nucleotide-based models.  If ancestralNucleotides is F, the output will not contain ancestral nucleotide information, and so the ancestral sequence will not be restored correctly if the saved file is loaded with readPopulationFile().  This option is provided because the ancestral sequence may be quite large, for models with a long chromosome (e.g., 1 GB if the chromosome is 109 bases long, when saved in text format, or 0.25 GB when saved in binary format).  If the model is not nucleotide-based (as enabled with the nucleotideBased parameter to initializeSLiMOptions()), the ancestralNucleotides parameter will be ignored.  Note that in nucleotide-based models the output format will always include the nucleotides associated with any nucleotide-based mutations; the ancestralNucleotides flag governs only the ancestral sequence.

Beginning with SLiM 3.5, the pedigreeIDs parameter may be used to request that pedigree IDs be written out (and read in by readFromPopulationFile(), subsequently).  This option is turned off (F) by default, for brevity.  This option may only be used if SLiM’s optional pedigree tracking has been enabled with initializeSLiMOptions(keepPedigrees=T).

Beginning with SLiM 5.0, the objectTags parameter may be used to request that tag values for objects be written out.  This option is turned off (F) by default, for brevity; if it turned on (T), the values of all tags for all objects of supported classes (Chromosome, Subpopulation, Individual, Haplosome, Mutation, Substitution) will be written.  For individuals, the tag, tagF, tagL0, tagL1, tagL2, tagL3, and tagL4 properties will be written; for chromosomes, subpopulations, haplosomes, and mutations, the tag property will be written.  The saved tag information can be read in by readFromPopulationFile(), but only if the output is in binary format (binary=T).  Note that if there is other state that you wish you persist, such as tags on objects of other classes, values attached to objects with setValue(), and so forth, you should persist that state in separate files using calls such as writeFile().

Beginning with SLiM 5.0, the substitutions parameter may be used to request that information about Substitution objects in the simulation be written out.  This option is turned off (F) by default, for brevity.  The saved substitution information can be read in by readFromPopulationFile(), but only if the output is in binary format (binary=T).

@@ -1234,7 +1238,7 @@

Returns a vector containing n points that are derived from point by adding a deviation drawn from a dispersal kernel (specified by maxDistance, functionType, and the ellipsis parameters ..., as detailed below) and then applying a boundary condition specified by boundary.  This method therefore performs the steps of a simple dispersal algorithm in a single vectorized call.  See deviatePositions() for an even more efficient approach.

The parameter point may contain a single point which is deviated and bounded n independent times, or may contain n points each of which is deviated and bounded.  In any case, each point in point should match the dimensionality of the model – one element in a 1D model, two elements in a 2D model, or three elements in a 3D model.  This method should not be called in a non-spatial model.

The dispersal kernel is specified similarly to other kernel-based methods, such as setInteractionFunction() and smooth().  For pointDeviated(), functionType may be "f" with no ellipsis arguments ... to use a flat kernel out to maxDistance; "l" with no ellipsis arguments for a kernel that decreases linearly from the center to zero at maxDistance; "e", in which case the ellipsis should supply a numeric$ lambda (rate) parameter for a negative exponential function; "n", in which case the ellipsis should supply a numeric$ sigma (standard deviation) parameter for a Gaussian function; or "t", in which case the ellipsis should supply a numeric$ degrees of freedom and a numeric$ scale parameter for a t-distribution function.  The Cauchy ("c") kernel is not supported by pointDeviated() since it is not well-behaved for this purpose, and the Student’s t ("t") kernel is not allowed in 3D models at present simply because it hasn’t been implemented.  See the InteractionType class documentation for more detailed discussion of the available kernel types and their parameters and probability distribution functions.  For pointDeviated(), the ellipsis parameters that follow functionType may each, independently, be either a singleton or a vector of length equal to n.  This allows each point to be deviated with a different kernel, representing, for example, the movements of individuals with differing dispersal capabilities/propensities.  (However, other parameters such as boundary, maxDistance, and functionType must be the same for all of the points, in the present design.)

-

The random points returned from this method are drawn from the probability distribution that is radially symmetric and has density proportional to the kernel – in other words, at distance r the density is proportional to the kernel type referred to by functionType.  (Said another way, the shape of the cross-section through the probability density function is given by the kernel.)  For instance, the value of the type "e" (exponential) kernel with rate a at r is proportional to exp(−ar), and so in 2D, the probability density that this method with kernel type "e" draws from has density proportional to p(xy) = exp(−a sqrt(x2 + y2)), since r = sqrt(x2 + y2) is the distance.  Note that the distribution of the distance is not given by the kernel except in 1D: in the type "e" example, the distribution of the distance in 1D is exponential, while in 2D it has density proportional to r exp(−ar) (i.e., Gamma with shape parameter 1).  For another example, the value of the type "n" (Normal) kernel at r with standard deviation 1 is proportional to exp(−r2 / 2), and so the density is proportional to p(xy) = exp(−(x2 + y2) / 2).  This is the standard bivariate Normal, and equivalent to drawing independent Normals for the x and y directions; however, the Normal is the only distribution for which independent draws along each axis will result in a radially symmetric distribution.  The distribution of the distance in 2D with type "n" is proportional to r exp(−r2 / 2), i.e., Rayleigh.

+

The random points returned from this method are drawn from the probability distribution that is radially symmetric and has density proportional to the kernel – in other words, at distance r the density is proportional to the kernel type referred to by functionType.  (Said another way, the shape of the cross-section through the probability density function is given by the kernel.)  For instance, the value of the type "e" (exponential) kernel with rate a at r is proportional to exp(−ar), and so in 2D, the probability density that this method with kernel type "e" draws from has density proportional to p(xy) = exp(−a sqrt(x2 + y2)), since r = sqrt(x2 + y2) is the distance.  Note that the distribution of the distance is not given by the kernel except in 1D: in the type "e" example, the distribution of the distance in 1D is exponential, while in 2D it has density proportional to r exp(−ar) (i.e., Gamma with shape parameter 1).  For another example, the value of the type "n" (Normal) kernel at r with standard deviation 1 is proportional to exp(−r2 / 2), and so the density is proportional to p(xy) = exp(−(x2 + y2) / 2).  This is the standard bivariate Normal, and equivalent to drawing independent Normals for the x and y directions; however, the Normal is the only distribution for which independent draws along each axis will result in a radially symmetric distribution.  The distribution of the distance in 2D with type "n" is proportional to r exp(−r2 / 2), i.e., Rayleigh.

The boundary condition must be one of "none", "periodic", "reflecting", "stopping", or "reprising".  For "none", no boundary condition is enforced; the deviated points are simply returned as is.  For "periodic", "reflecting", and "stopping", the boundary condition is enforced just as it is by the pointPeriodic(), pointReflected(), and pointStopped() methods; see their documentation for further details.  For "reprising", if the deviated point is out of bounds a new deviated point will be chosen, based upon the same original point, until a point inside bounds is obtained.  Note that absorbing boundaries (for which being out-of-bounds is lethal) would need to be implemented in script; this method cannot enforce them.  (Note, however, that the deviatePositions() method of Subpopulation can enforce absorbing boundaries.)

Note that for the typical usage case, in which point comes from the spatialPosition property for a vector of individuals, and the result is then set back onto the same vector of individuals using the setSpatialPosition() method, the deviatePositions() method provides an even more efficient alternative.

– (logical)pointInBounds(float point)

@@ -1293,6 +1297,8 @@

5.18.1  Substitution properties

chromosome => (object<Chromosome>$)

The Chromosome object with which the mutation is associated.

+

dominanceCoeff => (float$)

+

The dominance coefficient of the mutation, carried over from the original mutation object.

id => (integer$)

The identifier for this mutation.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run, and that identifier is carried over to the Substitution object when the mutation fixes.

fixationTick => (integer$)

@@ -1307,8 +1313,8 @@

The tick in which this mutation arose.

position => (integer$)

The position in the chromosome of this mutation.

-

selectionCoeff => (float$)

-

The selection coefficient of the mutation, drawn from the distribution of fitness effects of its MutationType.

+

selectionCoeff => (float$)

+

The selection coefficient of the mutation, carried over from the original mutation object.

subpopID <–> (integer$)

The identifier of the subpopulation in which this mutation arose.  This value is carried over from the Mutation object directly; if a “tag” value was used in the Mutation object, that value will carry over to the corresponding Substitution object.  The subpopID in Substitution is a read-write property to allow it to be used as a “tag” in the same way, if the origin subpopulation identifier is not needed.

tag <–> (integer$)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index f41ebda0..acbb06da 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -5900,6 +5900,41 @@ You can get the \f4\fs20 object with which the mutation is associated.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 dominanceCoeff => (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The dominance coefficient of the mutation, taken from the default dominance coefficient of its +\f3\fs18 MutationType +\f4\fs20 . If a mutation has a +\f3\fs18 selectionCoeff +\f4\fs20 of +\f1\i s +\f4\i0 and a +\f3\fs18 dominanceCoeff +\f4\fs20 of +\f1\i h +\f4\i0 , the multiplicative fitness effect of the mutation in a homozygote is 1+ +\f1\i s +\f4\i0 , and in a heterozygote is 1+ +\f1\i hs +\f4\i0 . The dominance coefficient of a mutation can be changed with the +\f3\fs18 setDominanceCoeff() +\f4\fs20 method.\ +Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +\f3\fs18 mut +\f4\fs20 \'92s dominance coefficient to some number +\f3\fs18 x +\f4\fs20 , +\f3\fs18 mut.dominanceCoeff==x +\f4\fs20 may be +\f3\fs18 F +\f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly. Instead, it is recommended to use the +\f3\fs18 id +\f4\fs20 or +\f3\fs18 tag +\f4\fs20 properties to identify particular mutations.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf0 id => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -5993,27 +6028,27 @@ nucleotide <\'96> (string$)\ \f5 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 selectionCoeff => (float$)\ +\f3\fs18 \cf2 selectionCoeff => (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The selection coefficient of the mutation, drawn from the distribution of fitness effects of its +\f4\fs20 \cf2 The selection coefficient of the mutation, drawn from the distribution of fitness effects of its \f3\fs18 MutationType -\f5\fs20 . -\f4 \cf2 \expnd0\expndtw0\kerning0 - If a mutation has a +\f4\fs20 . If a mutation has a \f3\fs18 selectionCoeff \f4\fs20 of \f1\i s +\f4\i0 and a +\f3\fs18 dominanceCoeff +\f4\fs20 of +\f1\i h \f4\i0 , the multiplicative fitness effect of the mutation in a homozygote is 1+ \f1\i s -\f4\i0 ; in a heterozygote it is 1+ +\f4\i0 , and in a heterozygote is 1+ \f1\i hs -\f4\i0 , where -\f1\i h -\f4\i0 is the dominance coefficient kept by the mutation type. -\f5 \cf0 \kerning1\expnd0\expndtw0 \ - -\f4 Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +\f4\i0 . The selection coefficient of a mutation can be changed with the +\f3\fs18 setSelectionCoeff() +\f4\fs20 method.\ +Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s selection coefficient to some number \f3\fs18 x @@ -6025,8 +6060,7 @@ nucleotide <\'96> (string$)\ \f3\fs18 id \f4\fs20 or \f3\fs18 tag -\f4\fs20 properties to identify particular mutations. -\f5 \ +\f4\fs20 properties to identify particular mutations.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 subpopID <\'96> (integer$)\ @@ -6064,39 +6098,52 @@ If you don\'92t care which subpopulation a mutation originated in, the \f1\fs22 methods\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf0 \'96\'a0(void)setMutationType(io$\'a0mutType) +\f3\i0\fs18 \cf2 \'96\'a0(void)setDominanceCoeff(float$\'a0dominanceCoeff)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Set the dominance coefficient of the mutation to +\f3\fs18 dominanceCoeff +\f4\fs20 . The dominance coefficient will be changed for all individuals that possess the mutation, since they all share a single +\f3\fs18 Mutation +\f4\fs20 object (note that the selection coefficient will remain unchanged).\ +Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the +\f3\fs18 Species +\f4\fs20 method +\f3\fs18 recalculateFitness() +\f4\fs20 \'96 but see the documentation of that method for caveats.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf0 \'96\'a0(void)setMutationType(io$\'a0mutType) \f5 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 Set the mutation type of the mutation to +\f4\fs20 \cf2 Set the mutation type of the mutation to \f3\fs18 mutType \f4\fs20 (which may be specified as either an \f3\fs18 integer \f4\fs20 identifier or a \f3\fs18 MutationType -\f4\fs20 object). This implicitly changes the dominance coefficient of the mutation to that of the new mutation type, since the dominance coefficient is a property of the mutation type. On the other hand, the selection coefficient of the mutation is not changed, since it is a property of the mutation object itself; it can be changed explicitly using the +\f4\fs20 object). The selection coefficients and dominance coefficients of existing mutations are not changed, since they are properties of the mutation objects themselves; they can be changed explicitly using the \f3\fs18 setSelectionCoeff() -\f4\fs20 method if so desired.\ -The mutation type of a mutation is normally a constant in simulations, so be sure you know what you are doing. Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the -\f3\fs18 Species -\f4\fs20 method -\f3\fs18 recalculateFitness() -\f4\fs20 \'96 but see the documentation of that method for caveats.\ +\f4\fs20 and +\f3\fs18 setDominanceCoeff() +\f4\fs20 methods of +\f3\fs18 Mutation +\f4\fs20 if so desired.\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf2 \expnd0\expndtw0\kerning0 In nucleotide-based models, a restriction applies: nucleotide-based mutations may not be changed to a non-nucleotide-based mutation type, and non-nucleotide-based mutations may not be changed to a nucleotide-based mutation type.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 \kerning1\expnd0\expndtw0 \'96\'a0(void)setSelectionCoeff(float$\'a0selectionCoeff) -\f5 \ +\f3\fs18 \cf2 \kerning1\expnd0\expndtw0 \'96\'a0(void)setSelectionCoeff(float$\'a0selectionCoeff)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 Set the selection coefficient of the mutation to +\f4\fs20 \cf2 Set the selection coefficient of the mutation to \f3\fs18 selectionCoeff \f4\fs20 . The selection coefficient will be changed for all individuals that possess the mutation, since they all share a single \f3\fs18 Mutation -\f4\fs20 object (note that the dominance coefficient will remain unchanged, as it is determined by the mutation type).\ -This is normally a constant in simulations, so be sure you know what you are doing; often setting up a +\f4\fs20 object (note that the dominance coefficient will remain unchanged).\ +Often setting up a \f3\fs18 mutationEffect() \f4\fs20 callback is preferable, in order to modify the selection coefficient in a more limited and controlled fashion. Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the \f3\fs18 Species @@ -6394,14 +6441,16 @@ Note that these distributions can in principle produce selection coefficients sm \fs20 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 dominanceCoeff <\'96> (float$)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\f3\fs18 \cf2 dominanceCoeff <\'96> (float$)\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The dominance coefficient used for mutations of this type when heterozygous. Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the -\f3\fs18 Species +\f4\fs20 \cf2 The default dominance coefficient used for mutations of this type when heterozygous. This default value is taken by new mutations of this mutation type when they are created, as the value of their +\f3\fs18 dominanceCoeff +\f4\fs20 property, but that can be changed later with the +\f3\fs18 Mutation \f4\fs20 method -\f3\fs18 recalculateFitness() -\f4\fs20 \'96 but see the documentation of that method for caveats.\ +\f3\fs18 setDominanceCoeff() +\f4\fs20 .\ Note that the dominance coefficient is not bounded. A dominance coefficient greater than \f3\fs18 1.0 \f4\fs20 may be used to achieve an overdominance effect. By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ @@ -6417,8 +6466,7 @@ Note that this property has a quirk: it is stored internally in SLiM using a sin \f3\fs18 id \f4\fs20 or \f3\fs18 tag -\f4\fs20 properties to identify particular mutation types. -\f5 \ +\f4\fs20 properties to identify particular mutation types.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 hemizygousDominanceCoeff <\'96> (float$)\ @@ -6428,7 +6476,7 @@ Note that this property has a quirk: it is stored internally in SLiM using a sin \f3\fs18 1.0 \f4\fs20 , and is used only in models where null haplosomes are present; the \f3\fs18 dominanceCoeff -\f4\fs20 property is the dominance coefficient used in most circumstances. Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the +\f4\fs20 property of the mutation is the dominance coefficient used in most circumstances. Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the \f3\fs18 Species \f4\fs20 method \f3\fs18 recalculateFitness() @@ -13288,6 +13336,12 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 object with which the mutation is associated.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 dominanceCoeff => (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The dominance coefficient of the mutation, carried over from the original mutation object.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf0 id => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -13360,12 +13414,10 @@ nucleotide <\'96> (string$)\ \f5 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 selectionCoeff => (float$)\ +\f3\fs18 \cf2 selectionCoeff => (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The selection coefficient of the mutation, drawn from the distribution of fitness effects of its -\f3\fs18 MutationType -\f5\fs20 .\ +\f4\fs20 \cf2 The selection coefficient of the mutation, carried over from the original mutation object.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 subpopID <\'96> (integer$)\ diff --git a/SLiMgui/SLiMWindowController.mm b/SLiMgui/SLiMWindowController.mm index 7fcd6875..03f85915 100644 --- a/SLiMgui/SLiMWindowController.mm +++ b/SLiMgui/SLiMWindowController.mm @@ -4419,7 +4419,7 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu } else if (aTableColumn == mutTypeDominanceColumn) { - return [NSString stringWithFormat:@"%.3f", mutationType->dominance_coeff_]; + return [NSString stringWithFormat:@"%.3f", mutationType->default_dominance_coeff_]; } else if (aTableColumn == mutTypeDFETypeColumn) { diff --git a/VERSIONS b/VERSIONS index 23f878b0..25cc6057 100644 --- a/VERSIONS +++ b/VERSIONS @@ -32,6 +32,9 @@ multitrait branch: tag <-> (integer$) type => (string$) add Community property allTraits => (object) + add a dominanceCoeff property to Mutation, with a value inherited from MutationType's property (which is now just the default value) + add dominanceCoeff properties to Mutation and Substitution + add a setDominanceCoeff() method to Mutation, yay! version 5.0 (Eidos version 4.0): diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 040ec293..a88fb825 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1035,7 +1035,7 @@ MutationIndex Chromosome::DrawNewMutation(std::pairDrawSelectionCoefficient(); + slim_selcoeff_t selection_coeff = static_cast(mutation_type_ptr->DrawSelectionCoefficient()); // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE, SINCE WE DO NOT KNOW WHAT HAPLOSOME WE WILL BE INSERTED INTO! THIS IS THE CALLER'S RESPONSIBILITY! MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); @@ -1043,7 +1043,7 @@ MutationIndex Chromosome::DrawNewMutation(std::pairdefault_dominance_coeff_, p_subpop_index, p_tick, -1); // addition to the main registry and the muttype registries will happen if the new mutation clears the stacking policy @@ -1401,13 +1401,13 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pairDrawSelectionCoefficient(); + slim_selcoeff_t selection_coeff = static_cast(mutation_type_ptr->DrawSelectionCoefficient()); // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE! THIS IS THE CALLER'S RESPONSIBILITY! MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); Mutation *mutation = gSLiM_Mutation_Block + new_mut_index; - new (mutation) Mutation(mutation_type_ptr, index_, position, selection_coeff, p_subpop_index, p_tick, nucleotide); + new (mutation) Mutation(mutation_type_ptr, index_, position, selection_coeff, mutation_type_ptr->default_dominance_coeff_, p_subpop_index, p_tick, nucleotide); // Call mutation() callbacks if there are any if (p_mutation_callbacks) diff --git a/core/core.pro b/core/core.pro index 682077a1..87c11643 100644 --- a/core/core.pro +++ b/core/core.pro @@ -112,7 +112,8 @@ SOURCES += \ species.cpp \ species_eidos.cpp \ subpopulation.cpp \ - substitution.cpp + substitution.cpp \ + trait.cpp HEADERS += \ chromosome.h \ @@ -137,4 +138,5 @@ HEADERS += \ spatial_map.h \ species.h \ subpopulation.h \ - substitution.h + substitution.h \ + trait.h diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 6adf3f33..07e1439c 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -1955,7 +1955,7 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i { if (polymorphism != nuc_based.front()) p_out << ','; - p_out << polymorphism->mutation_ptr_->mutation_type_ptr_->dominance_coeff_; + p_out << polymorphism->mutation_ptr_->dominance_coeff_; } p_out << ";"; @@ -2080,7 +2080,7 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i // emit the INFO fields and the Genotype marker p_out << "MID=" << mutation->mutation_id_ << ";"; p_out << "S=" << mutation->selection_coeff_ << ";"; - p_out << "DOM=" << mutation->mutation_type_ptr_->dominance_coeff_ << ";"; + p_out << "DOM=" << mutation->dominance_coeff_ << ";"; p_out << "PO=" << mutation->subpop_index_ << ";"; p_out << "TO=" << mutation->origin_tick_ << ";"; p_out << "MT=" << mutation->mutation_type_ptr_->mutation_type_id_ << ";"; @@ -2897,7 +2897,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, selection_coeff, origin_subpop_id, origin_tick, (int8_t)nucleotide); + Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->default_dominance_coeff_, origin_subpop_id, origin_tick, (int8_t)nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DFE), so we set all_pure_neutral_DFE_ also @@ -3398,7 +3398,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, selection_coeff, subpop_index, origin_tick, nucleotide); + Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->default_dominance_coeff_, subpop_index, origin_tick, nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ if (selection_coeff != 0.0) @@ -3721,8 +3721,8 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt // parse/validate the INFO fields that we recognize std::vector info_substrs = Eidos_string_split(info_str, ";"); std::vector info_mutids; - std::vector info_selcoeffs; - std::vector info_domcoeffs; + std::vector info_selcoeffs; + std::vector info_domcoeffs; std::vector info_poporigin; std::vector info_tickorigin; std::vector info_muttype; @@ -3949,20 +3949,21 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt if (!mutation_type_ptr) EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file MT field missing, but no default mutation type was supplied in the mutationType parameter." << EidosTerminate(); - // check the dominance coefficient of DOM against that of the mutation type + // get the dominance coefficient from DOM, or use the default coefficient from the mutation type + slim_selcoeff_t dominance_coeff; + if (info_domcoeffs.size() > 0) - { - if (std::abs(info_domcoeffs[alt_allele_index] - mutation_type_ptr->dominance_coeff_) > 0.0001) - EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file DOM field specifies a dominance coefficient " << info_domcoeffs[alt_allele_index] << " that differs from the mutation type's dominance coefficient of " << mutation_type_ptr->dominance_coeff_ << "." << EidosTerminate(); - } + dominance_coeff = info_domcoeffs[alt_allele_index]; + else + dominance_coeff = mutation_type_ptr->default_dominance_coeff_; - // get the selection coefficient from S, or draw one - double selection_coeff; + // get the selection coefficient from S, or draw one from the mutation type + slim_selcoeff_t selection_coeff; if (info_selcoeffs.size() > 0) selection_coeff = info_selcoeffs[alt_allele_index]; else - selection_coeff = mutation_type_ptr->DrawSelectionCoefficient(); + selection_coeff = static_cast(mutation_type_ptr->DrawSelectionCoefficient()); // get the subpop index from PO, or set to -1; no bounds checking on this slim_objectid_t subpop_index = -1; @@ -4044,12 +4045,12 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt // a mutation ID was supplied; we use it blindly, having checked above that we are in the case where this is legal slim_mutationid_t mut_mutid = info_mutids[alt_allele_index]; - new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mut_mutid, mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, subpop_index, origin_tick, nucleotide); + new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mut_mutid, mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } else { // no mutation ID supplied, so use whatever is next - new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, subpop_index, origin_tick, nucleotide); + new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ diff --git a/core/individual.cpp b/core/individual.cpp index a651a867..5be14b3c 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -4439,8 +4439,8 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal // parse/validate the INFO fields that we recognize std::vector info_substrs = Eidos_string_split(info_str, ";"); std::vector info_mutids; - std::vector info_selcoeffs; - std::vector info_domcoeffs; + std::vector info_selcoeffs; + std::vector info_domcoeffs; std::vector info_poporigin; std::vector info_tickorigin; std::vector info_muttype; @@ -4564,20 +4564,21 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (!mutation_type_ptr) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file MT field missing, but no default mutation type was supplied in the mutationType parameter." << EidosTerminate(); - // check the dominance coefficient of DOM against that of the mutation type + // get the dominance coefficient from DOM, or use the default coefficient from the mutation type + slim_selcoeff_t dominance_coeff; + if (info_domcoeffs.size() > 0) - { - if (std::abs(info_domcoeffs[alt_allele_index] - mutation_type_ptr->dominance_coeff_) > 0.0001) - EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file DOM field specifies a dominance coefficient " << info_domcoeffs[alt_allele_index] << " that differs from the mutation type's dominance coefficient of " << mutation_type_ptr->dominance_coeff_ << "." << EidosTerminate(); - } + dominance_coeff = info_domcoeffs[alt_allele_index]; + else + dominance_coeff = mutation_type_ptr->default_dominance_coeff_; - // get the selection coefficient from S, or draw one - double selection_coeff; + // get the selection coefficient from S, or draw one from the mutation type + slim_selcoeff_t selection_coeff; if (info_selcoeffs.size() > 0) selection_coeff = info_selcoeffs[alt_allele_index]; else - selection_coeff = mutation_type_ptr->DrawSelectionCoefficient(); + selection_coeff = static_cast(mutation_type_ptr->DrawSelectionCoefficient()); // get the subpop index from PO, or set to -1; no bounds checking on this slim_objectid_t subpop_index = -1; @@ -4659,12 +4660,12 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal // a mutation ID was supplied; we use it blindly, having checked above that we are in the case where this is legal slim_mutationid_t mut_mutid = info_mutids[alt_allele_index]; - new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mut_mutid, mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, subpop_index, origin_tick, nucleotide); + new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mut_mutid, mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } else { // no mutation ID supplied, so use whatever is next - new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, subpop_index, origin_tick, nucleotide); + new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ diff --git a/core/mutation.cpp b/core/mutation.cpp index dfd4085c..284200da 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -229,8 +229,8 @@ size_t SLiMMemoryUsageForMutationRefcounts(void) // A global counter used to assign all Mutation objects a unique ID slim_mutationid_t gSLiM_next_mutation_id = 0; -Mutation::Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, double p_selection_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : -mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(static_cast(p_selection_coeff)), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(gSLiM_next_mutation_id++) +Mutation::Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : +mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(p_selection_coeff), dominance_coeff_(p_dominance_coeff), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(gSLiM_next_mutation_id++) { #ifdef DEBUG_LOCKS_ENABLED gSLiM_Mutation_LOCK.start_critical(2); @@ -241,7 +241,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ // cache values used by the fitness calculation code for speed; see header cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->dominance_coeff_ * selection_coeff_); + cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); // zero out our refcount, which is now kept in a separate buffer @@ -300,15 +300,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ #endif } -Mutation::Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, double p_selection_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : -mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(static_cast(p_selection_coeff)), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id) +Mutation::Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : +mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(p_selection_coeff), dominance_coeff_(p_dominance_coeff), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id) { // initialize the tag to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; // cache values used by the fitness calculation code for speed; see header cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->dominance_coeff_ * selection_coeff_); + cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); // zero out our refcount, which is now kept in a separate buffer @@ -391,6 +391,8 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(position_)); case gID_selectionCoeff: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selection_coeff_)); + case gID_dominanceCoeff: // ACCELERATED + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance_coeff_)); // variables case gID_nucleotide: // ACCELERATED @@ -600,6 +602,20 @@ EidosValue *Mutation::GetProperty_Accelerated_selectionCoeff(EidosObject **p_val return float_result; } +EidosValue *Mutation::GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size) +{ + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + Mutation *value = (Mutation *)(p_values[value_index]); + + float_result->set_float_no_check(value->dominance_coeff_, value_index); + } + + return float_result; +} + EidosValue *Mutation::GetProperty_Accelerated_mutationType(EidosObject **p_values, size_t p_values_size) { EidosValue_Object *object_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_MutationType_Class))->resize_no_initialize(p_values_size); @@ -709,6 +725,7 @@ EidosValue_SP Mutation::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, c switch (p_method_id) { case gID_setSelectionCoeff: return ExecuteMethod_setSelectionCoeff(p_method_id, p_arguments, p_interpreter); + case gID_setDominanceCoeff: return ExecuteMethod_setDominanceCoeff(p_method_id, p_arguments, p_interpreter); case gID_setMutationType: return ExecuteMethod_setMutationType(p_method_id, p_arguments, p_interpreter); default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } @@ -752,12 +769,31 @@ EidosValue_SP Mutation::ExecuteMethod_setSelectionCoeff(EidosGlobalStringID p_me // cache values used by the fitness calculation code for speed; see header cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->dominance_coeff_ * selection_coeff_); + cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); return gStaticEidosValueVOID; } +// ********************* - (void)setDominanceCoeff(float$ dominanceCoeff) +// +EidosValue_SP Mutation::ExecuteMethod_setDominanceCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *dominanceCoeff_value = p_arguments[0].get(); + + double value = dominanceCoeff_value->FloatAtIndex_NOCAST(0, nullptr); + + dominance_coeff_ = static_cast(value); // intentionally no bounds check + + // cache values used by the fitness calculation code for speed; see header + //cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); + cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); + //cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + + return gStaticEidosValueVOID; +} + // ********************* - (void)setMutationType(io$ mutType) // EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -780,7 +816,7 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth // cache values used by the fitness calculation code for speed; see header cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->dominance_coeff_ * selection_coeff_); + cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); return gStaticEidosValueVOID; @@ -817,6 +853,7 @@ const std::vector *Mutation_Class::Properties(void) properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_originTick)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_position)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_selectionCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_selectionCoeff)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominanceCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_dominanceCoeff)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_subpopID)->DeclareAcceleratedSet(Mutation::SetProperty_Accelerated_subpopID)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_tag)->DeclareAcceleratedSet(Mutation::SetProperty_Accelerated_tag)); @@ -837,6 +874,7 @@ const std::vector *Mutation_Class::Methods(void) const methods = new std::vector(*super::Methods()); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setSelectionCoeff, kEidosValueMaskVOID))->AddFloat_S("selectionCoeff")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDominanceCoeff, kEidosValueMaskVOID))->AddFloat_S("dominanceCoeff")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setMutationType, kEidosValueMaskVOID))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); diff --git a/core/mutation.h b/core/mutation.h index 755685c3..99b8edc9 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -76,7 +76,8 @@ class Mutation : public EidosDictionaryRetained MutationType *mutation_type_ptr_; // mutation type identifier const slim_position_t position_; // position on the chromosome - slim_selcoeff_t selection_coeff_; // selection coefficient – not const because it may be changed in script + slim_selcoeff_t selection_coeff_; // selection coefficient (s) + slim_selcoeff_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default slim_objectid_t subpop_index_; // subpopulation in which mutation arose (or a user-defined tag value!) const slim_tick_t origin_tick_; // tick in which the mutation arose slim_chromosome_index_t chromosome_index_; // the (uint8_t) index of this mutation's chromosome @@ -98,7 +99,6 @@ class Mutation : public EidosDictionaryRetained slim_selcoeff_t cached_one_plus_sel_; // a cached value for (1 + selection_coeff_), clamped to 0.0 minimum slim_selcoeff_t cached_one_plus_dom_sel_; // a cached value for (1 + dominance_coeff * selection_coeff_), clamped to 0.0 minimum slim_selcoeff_t cached_one_plus_hemizygousdom_sel_; // a cached value for (1 + hemizygous_dominance_coeff_ * selection_coeff_), clamped to 0.0 minimum - // NOTE THERE ARE 4 BYTES FREE IN THE CLASS LAYOUT HERE; see Mutation::Mutation() and Mutation layout.graffle #if DEBUG mutable slim_refcount_t refcount_CHECK_; // scratch space for checking of parallel refcounting @@ -107,8 +107,8 @@ class Mutation : public EidosDictionaryRetained Mutation(const Mutation&) = delete; // no copying Mutation& operator=(const Mutation&) = delete; // no copying Mutation(void) = delete; // no null construction; Mutation is an immutable class - Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, double p_selection_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); - Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, double p_selection_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); + Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); + Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline #if DEBUG_MUTATIONS @@ -134,6 +134,7 @@ class Mutation : public EidosDictionaryRetained virtual void SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) override; virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; EidosValue_SP ExecuteMethod_setSelectionCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_setDominanceCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism @@ -147,6 +148,7 @@ class Mutation : public EidosDictionaryRetained static EidosValue *GetProperty_Accelerated_subpopID(EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_selectionCoeff(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_mutationType(EidosObject **p_values, size_t p_values_size); // Accelerated property writing; see class EidosObject for comments on this mechanism diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index f7bbed6d..457fe9f8 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -61,7 +61,7 @@ MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_i MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DFEType p_dfe_type, std::vector p_dfe_parameters, std::vector p_dfe_strings) : #endif self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStringWithPrefix('m', p_mutation_type_id)), EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(this, gSLiM_MutationType_Class))), - species_(p_species), mutation_type_id_(p_mutation_type_id), dominance_coeff_(static_cast(p_dominance_coeff)), hemizygous_dominance_coeff_(1.0), dfe_type_(p_dfe_type), dfe_parameters_(std::move(p_dfe_parameters)), dfe_strings_(std::move(p_dfe_strings)), nucleotide_based_(p_nuc_based), convert_to_substitution_(false), stack_policy_(MutationStackPolicy::kStack), stack_group_(p_mutation_type_id), cached_dfe_script_(nullptr) + species_(p_species), mutation_type_id_(p_mutation_type_id), default_dominance_coeff_(static_cast(p_dominance_coeff)), hemizygous_dominance_coeff_(1.0), dfe_type_(p_dfe_type), dfe_parameters_(std::move(p_dfe_parameters)), dfe_strings_(std::move(p_dfe_strings)), nucleotide_based_(p_nuc_based), convert_to_substitution_(false), stack_policy_(MutationStackPolicy::kStack), stack_group_(p_mutation_type_id), cached_dfe_script_(nullptr) #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES , muttype_registry_call_count_(0), keeping_muttype_registry_(false) #endif @@ -377,7 +377,7 @@ double MutationType::DrawSelectionCoefficient(void) const // This is unused except by debugging code and in the debugger itself std::ostream &operator<<(std::ostream &p_outstream, const MutationType &p_mutation_type) { - p_outstream << "MutationType{dominance_coeff_ " << p_mutation_type.dominance_coeff_ << ", dfe_type_ '" << p_mutation_type.dfe_type_ << "', dfe_parameters_ <"; + p_outstream << "MutationType{default_dominance_coeff_ " << p_mutation_type.default_dominance_coeff_ << ", dfe_type_ '" << p_mutation_type.dfe_type_ << "', dfe_parameters_ <"; if (p_mutation_type.dfe_parameters_.size() > 0) { @@ -491,7 +491,7 @@ EidosValue_SP MutationType::GetProperty(EidosGlobalStringID p_property_id) case gID_convertToSubstitution: return (convert_to_substitution_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_dominanceCoeff: // ACCELERATED - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance_coeff_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(default_dominance_coeff_)); case gID_hemizygousDominanceCoeff: return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(hemizygous_dominance_coeff_)); case gID_mutationStackGroup: @@ -580,7 +580,7 @@ EidosValue *MutationType::GetProperty_Accelerated_dominanceCoeff(EidosObject **p { MutationType *value = (MutationType *)(p_values[value_index]); - float_result->set_float_no_check(value->dominance_coeff_, value_index); + float_result->set_float_no_check(value->default_dominance_coeff_, value_index); } return float_result; @@ -619,11 +619,13 @@ void MutationType::SetProperty(EidosGlobalStringID p_property_id, const EidosVal { double value = p_value.FloatAtIndex_NOCAST(0, nullptr); - dominance_coeff_ = static_cast(value); // intentionally no bounds check + default_dominance_coeff_ = static_cast(value); // intentionally no bounds check - // Changing the dominance coefficient means that the cached fitness effects of all mutations using this type - // become invalid. We set a flag here to indicate that values that depend on us need to be recached. - species_.any_dominance_coeff_changed_ = true; + // BCH 7/2/2025: Changing the default dominance coefficient no longer means that the cached fitness + // effects of all mutations using this type become invalid; it is now just the *default* coefficient, + // and changing it does not change the state of mutations that have already derived from it. We do + // still want to let the community know that a mutation type has changed, though. + //species_.any_dominance_coeff_changed_ = true; species_.community_.mutation_types_changed_ = true; return; diff --git a/core/mutation_type.h b/core/mutation_type.h index 3c02ce9c..122ed7fd 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -85,7 +85,7 @@ class MutationType : public EidosDictionaryUnretained slim_objectid_t mutation_type_id_; // the id by which this mutation type is indexed in the chromosome EidosValue_SP cached_value_muttype_id_; // a cached value for mutation_type_id_; reset() if that changes - slim_selcoeff_t dominance_coeff_; // dominance coefficient (h) + slim_selcoeff_t default_dominance_coeff_; // the default dominance coefficient (h) inherited by mutations of this type slim_selcoeff_t hemizygous_dominance_coeff_; // dominance coefficient (h) used when one haplosome is null DFEType dfe_type_; // distribution of fitness effects (DFE) type (f: fixed, g: gamma, e: exponential, n: normal, w: Weibull) diff --git a/core/polymorphism.cpp b/core/polymorphism.cpp index 975bb14e..8a5d5855 100644 --- a/core/polymorphism.cpp +++ b/core/polymorphism.cpp @@ -50,7 +50,7 @@ void Polymorphism::Print_ID_Tag(std::ostream &p_out) const p_out << " "; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mutation_ptr_->mutation_type_ptr_->dominance_coeff_); // necessary precision for non-lossiness + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mutation_ptr_->dominance_coeff_); // necessary precision for non-lossiness p_out << double_buf; p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -85,7 +85,7 @@ void Polymorphism::Print_ID(std::ostream &p_out) const p_out << " "; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mutation_ptr_->mutation_type_ptr_->dominance_coeff_); // necessary precision for non-lossiness + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mutation_ptr_->dominance_coeff_); // necessary precision for non-lossiness p_out << double_buf; p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -116,7 +116,7 @@ void Polymorphism::Print_NoID_Tag(std::ostream &p_out) const } // and then the remainder of the output line - p_out << " " << mutation_ptr_->selection_coeff_ << " " << mutation_ptr_->mutation_type_ptr_->dominance_coeff_ << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; + p_out << " " << mutation_ptr_->selection_coeff_ << " " << mutation_ptr_->dominance_coeff_ << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; // output a nucleotide if available if (mutation_ptr_->mutation_type_ptr_->nucleotide_based_) @@ -152,7 +152,7 @@ void Polymorphism::Print_NoID(std::ostream &p_out) const } // and then the remainder of the output line - p_out << " " << mutation_ptr_->selection_coeff_ << " " << mutation_ptr_->mutation_type_ptr_->dominance_coeff_ << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; + p_out << " " << mutation_ptr_->selection_coeff_ << " " << mutation_ptr_->dominance_coeff_ << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; // output a nucleotide if available if (mutation_ptr_->mutation_type_ptr_->nucleotide_based_) diff --git a/core/population.cpp b/core/population.cpp index 6e394372..d7645481 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5165,7 +5165,7 @@ void Population::ValidateMutationFitnessCaches(void) MutationIndex mut_index = (*registry_iter++); Mutation *mut = mut_block_ptr + mut_index; slim_selcoeff_t sel_coeff = mut->selection_coeff_; - slim_selcoeff_t dom_coeff = mut->mutation_type_ptr_->dominance_coeff_; + slim_selcoeff_t dom_coeff = mut->dominance_coeff_; slim_selcoeff_t hemizygous_dom_coeff = mut->mutation_type_ptr_->hemizygous_dominance_coeff_; mut->cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + sel_coeff); @@ -8100,7 +8100,7 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit slim_objectid_t mutation_type_id = mutation_type_ptr->mutation_type_id_; slim_position_t position = mutation_ptr->position_; slim_selcoeff_t selection_coeff = mutation_ptr->selection_coeff_; - slim_selcoeff_t dominance_coeff = mutation_type_ptr->dominance_coeff_; + slim_selcoeff_t dominance_coeff = mutation_ptr->dominance_coeff_; // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved; too edge to be bothered... slim_objectid_t subpop_index = mutation_ptr->subpop_index_; slim_tick_t origin_tick = mutation_ptr->origin_tick_; @@ -8257,7 +8257,7 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit slim_objectid_t mutation_type_id = mutation_type_ptr->mutation_type_id_; slim_position_t position = substitution_ptr->position_; slim_selcoeff_t selection_coeff = substitution_ptr->selection_coeff_; - slim_selcoeff_t dominance_coeff = mutation_type_ptr->dominance_coeff_; + slim_selcoeff_t dominance_coeff = substitution_ptr->dominance_coeff_; slim_objectid_t subpop_index = substitution_ptr->subpop_index_; slim_tick_t origin_tick = substitution_ptr->origin_tick_; slim_tick_t fixation_tick = substitution_ptr->fixation_tick_; diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 9e2ceebb..5b098814 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1376,6 +1376,7 @@ const std::string &gStr_setGenomicElementType = EidosRegisteredString("setGenomi const std::string &gStr_setMutationFractions = EidosRegisteredString("setMutationFractions", gID_setMutationFractions); const std::string &gStr_setMutationMatrix = EidosRegisteredString("setMutationMatrix", gID_setMutationMatrix); const std::string &gStr_setSelectionCoeff = EidosRegisteredString("setSelectionCoeff", gID_setSelectionCoeff); +const std::string &gStr_setDominanceCoeff = EidosRegisteredString("setDominanceCoeff", gID_setDominanceCoeff); const std::string &gStr_setMutationType = EidosRegisteredString("setMutationType", gID_setMutationType); const std::string &gStr_drawSelectionCoefficient = EidosRegisteredString("drawSelectionCoefficient", gID_drawSelectionCoefficient); const std::string &gStr_setDistribution = EidosRegisteredString("setDistribution", gID_setDistribution); diff --git a/core/slim_globals.h b/core/slim_globals.h index e252df2c..b618dca3 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -955,6 +955,7 @@ extern const std::string &gStr_setGenomicElementType; extern const std::string &gStr_setMutationFractions; extern const std::string &gStr_setMutationMatrix; extern const std::string &gStr_setSelectionCoeff; +extern const std::string &gStr_setDominanceCoeff; extern const std::string &gStr_setMutationType; extern const std::string &gStr_drawSelectionCoefficient; extern const std::string &gStr_setDistribution; @@ -1420,6 +1421,7 @@ enum _SLiMGlobalStringID : int { gID_setMutationFractions, gID_setMutationMatrix, gID_setSelectionCoeff, + gID_setDominanceCoeff, gID_setMutationType, gID_drawSelectionCoefficient, gID_setDistribution, diff --git a/core/species.cpp b/core/species.cpp index 78d7e49d..b636901a 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1262,10 +1262,10 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos slim_position_t position = SLiMCastToPositionTypeOrRaise(position_long); iss >> sub; - double selection_coeff = EidosInterpreter::FloatForString(sub, nullptr); + slim_selcoeff_t selection_coeff = static_cast(EidosInterpreter::FloatForString(sub, nullptr)); - iss >> sub; // dominance coefficient, which is given in the mutation type; we check below that the value read matches the mutation type - double dominance_coeff = EidosInterpreter::FloatForString(sub, nullptr); + iss >> sub; + slim_selcoeff_t dominance_coeff = static_cast(EidosInterpreter::FloatForString(sub, nullptr)); iss >> sub; slim_objectid_t subpop_index = SLiMEidosScript::ExtractIDFromStringWithPrefix(sub, 'p', nullptr); @@ -1293,8 +1293,7 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos if (!mutation_type_ptr) EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromTextFile): mutation type m"<< mutation_type_id << " has not been defined for this species." << EidosTerminate(); - if (!Eidos_ApproximatelyEqual(mutation_type_ptr->dominance_coeff_, dominance_coeff)) // a reasonable tolerance to allow for I/O roundoff - EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromTextFile): mutation type m"<< mutation_type_id << " has dominance coefficient " << mutation_type_ptr->dominance_coeff_ << " that does not match the population file dominance coefficient of " << dominance_coeff << "." << EidosTerminate(); + // BCH 7/2/2025: We no longer check the dominance coefficient against the mutation type, because it is allowed to differ // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved, or checked here; too edge to be bothered... @@ -1306,7 +1305,7 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos // construct the new mutation; NOTE THAT THE STACKING POLICY IS NOT CHECKED HERE, AS THIS IS NOT CONSIDERED THE ADDITION OF A MUTATION! MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, subpop_index, tick, nucleotide); + Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, dominance_coeff, subpop_index, tick, nucleotide); // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry mutations.emplace(polymorphism_id, new_mut_index); @@ -2052,8 +2051,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid if (!mutation_type_ptr) EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): mutation type m" << mutation_type_id << " has not been defined for this species." << EidosTerminate(); - if (mutation_type_ptr->dominance_coeff_ != dominance_coeff) // no tolerance, unlike _InitializePopulationFromTextFile(); should match exactly here since we used binary - EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): mutation type m" << mutation_type_id << " has dominance coefficient " << mutation_type_ptr->dominance_coeff_ << " that does not match the population file dominance coefficient of " << dominance_coeff << "." << EidosTerminate(); + // BCH 7/2/2025: We no longer check the dominance coefficient against the mutation type, because it is allowed to differ // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved, or checked here; too edge to be bothered... @@ -2065,7 +2063,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid // construct the new mutation; NOTE THAT THE STACKING POLICY IS NOT CHECKED HERE, AS THIS IS NOT CONSIDERED THE ADDITION OF A MUTATION! MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, subpop_index, tick, nucleotide); + Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, dominance_coeff, subpop_index, tick, nucleotide); // read the tag value, if present if (has_object_tags) @@ -2357,8 +2355,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid if (!mutation_type_ptr) EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): mutation type m" << mutation_type_id << " has not been defined for this species." << EidosTerminate(); - if (mutation_type_ptr->dominance_coeff_ != dominance_coeff) // no tolerance, unlike _InitializePopulationFromTextFile(); should match exactly here since we used binary - EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): mutation type m" << mutation_type_id << " has dominance coefficient " << mutation_type_ptr->dominance_coeff_ << " that does not match the population file dominance coefficient of " << dominance_coeff << "." << EidosTerminate(); + // BCH 7/2/2025: We no longer check the dominance coefficient against the mutation type, because it is allowed to differ // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved, or checked here; too edge to be bothered... @@ -2368,7 +2365,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): mutation type m" << mutation_type_id << " is not nucleotide-based, but a nucleotide value for a mutation of this type was supplied." << EidosTerminate(); // construct the new substitution - Substitution *new_substitution = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, subpop_index, origin_tick, fixation_tick, nucleotide); + Substitution *new_substitution = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, dominance_coeff, subpop_index, origin_tick, fixation_tick, nucleotide); // read its tag, if requested if (has_object_tags) @@ -9709,7 +9706,8 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapconvert_to_substitution_)) { // this mutation is fixed, and the muttype wants substitutions, so make a substitution - Substitution *sub = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); + // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec + Substitution *sub = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->default_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); population_.treeseq_substitutions_map_.emplace(position, sub); population_.substitutions_.emplace_back(sub); @@ -9722,7 +9720,8 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapdefault_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry p_mutIndexMap[mutation_id] = new_mut_index; diff --git a/core/species.h b/core/species.h index 1cbd5950..4ce8a48a 100644 --- a/core/species.h +++ b/core/species.h @@ -97,6 +97,7 @@ enum class SLiMFileFormat typedef struct __attribute__((__packed__)) { slim_objectid_t mutation_type_id_; // 4 bytes (int32_t): the id of the mutation type the mutation belongs to slim_selcoeff_t selection_coeff_; // 4 bytes (float): the selection coefficient + // FIXME MULTITRAIT need to add a dominance_coeff_ property here! slim_objectid_t subpop_index_; // 4 bytes (int32_t): the id of the subpopulation in which the mutation arose slim_tick_t origin_tick_; // 4 bytes (int32_t): the tick in which the mutation arose int8_t nucleotide_; // 1 byte (int8_t): the nucleotide for the mutation (0='A', 1='C', 2='G', 3='T'), or -1 diff --git a/core/substitution.cpp b/core/substitution.cpp index 7d913f0b..cc4bebce 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -35,15 +35,15 @@ #pragma mark - Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : - EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), selection_coeff_(p_mutation.selection_coeff_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) + EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), selection_coeff_(p_mutation.selection_coeff_), dominance_coeff_(p_mutation.dominance_coeff_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) { AddKeysAndValuesFrom(&p_mutation); // No call to ContentsChanged() here; we know we use Dictionary not DataFrame, and Mutation already vetted the dictionary } -Substitution::Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, double p_selection_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide) : -mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(static_cast(p_selection_coeff)), subpop_index_(p_subpop_index), origin_tick_(p_tick), fixation_tick_(p_fixation_tick), chromosome_index_(p_chromosome_index), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id), tag_value_(SLIM_TAG_UNSET_VALUE) +Substitution::Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide) : +mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(static_cast(p_selection_coeff)), dominance_coeff_(static_cast(p_dominance_coeff)), subpop_index_(p_subpop_index), origin_tick_(p_tick), fixation_tick_(p_fixation_tick), chromosome_index_(p_chromosome_index), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id), tag_value_(SLIM_TAG_UNSET_VALUE) { } @@ -64,7 +64,7 @@ void Substitution::PrintForSLiMOutput(std::ostream &p_out) const } // and then the remainder of the output line - p_out << " " << selection_coeff_ << " " << mutation_type_ptr_->dominance_coeff_ << " p" << subpop_index_ << " " << origin_tick_ << " "<< fixation_tick_; + p_out << " " << selection_coeff_ << " " << dominance_coeff_ << " p" << subpop_index_ << " " << origin_tick_ << " "<< fixation_tick_; // output a nucleotide if available if (mutation_type_ptr_->nucleotide_based_) @@ -92,7 +92,7 @@ void Substitution::PrintForSLiMOutput_Tag(std::ostream &p_out) const } // and then the remainder of the output line - p_out << " " << selection_coeff_ << " " << mutation_type_ptr_->dominance_coeff_ << " p" << subpop_index_ << " " << origin_tick_ << " "<< fixation_tick_; + p_out << " " << selection_coeff_ << " " << dominance_coeff_ << " p" << subpop_index_ << " " << origin_tick_ << " "<< fixation_tick_; // output a nucleotide if available if (mutation_type_ptr_->nucleotide_based_) @@ -147,6 +147,8 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(position_)); case gID_selectionCoeff: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selection_coeff_)); + case gID_dominanceCoeff: // ACCELERATED + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance_coeff_)); case gID_originTick: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(origin_tick_)); case gID_fixationTick: // ACCELERATED @@ -346,6 +348,20 @@ EidosValue *Substitution::GetProperty_Accelerated_selectionCoeff(EidosObject **p return float_result; } +EidosValue *Substitution::GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size) +{ + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + Substitution *value = (Substitution *)(p_values[value_index]); + + float_result->set_float_no_check(value->dominance_coeff_, value_index); + } + + return float_result; +} + EidosValue *Substitution::GetProperty_Accelerated_mutationType(EidosObject **p_values, size_t p_values_size) { EidosValue_Object *object_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_MutationType_Class))->resize_no_initialize(p_values_size); @@ -447,6 +463,7 @@ const std::vector *Substitution_Class::Properties(vo properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_mutationType)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_position)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_selectionCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_selectionCoeff)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominanceCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_dominanceCoeff)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_subpopID)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotide, false, kEidosValueMaskString | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotide)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotideValue)); diff --git a/core/substitution.h b/core/substitution.h index b8dad5b0..142d2abd 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -49,7 +49,8 @@ class Substitution : public EidosDictionaryRetained MutationType *mutation_type_ptr_; // mutation type identifier slim_position_t position_; // position - slim_selcoeff_t selection_coeff_; // selection coefficient + slim_selcoeff_t selection_coeff_; // selection coefficient (s) + slim_selcoeff_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default slim_objectid_t subpop_index_; // subpopulation in which mutation arose slim_tick_t origin_tick_; // tick in which mutation arose slim_tick_t fixation_tick_; // tick in which mutation fixed @@ -62,7 +63,7 @@ class Substitution : public EidosDictionaryRetained Substitution& operator=(const Substitution&) = delete; // no copying Substitution(void) = delete; // no null construction Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick); // construct from the mutation that has fixed, and the tick in which it fixed - Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, double p_selection_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide); + Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide); // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline inline virtual ~Substitution(void) override { } @@ -90,6 +91,7 @@ class Substitution : public EidosDictionaryRetained static EidosValue *GetProperty_Accelerated_subpopID(EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_selectionCoeff(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_mutationType(EidosObject **p_values, size_t p_values_size); }; diff --git a/eidos/eidos_class_Object.cpp b/eidos/eidos_class_Object.cpp index da521dde..ab604b65 100644 --- a/eidos/eidos_class_Object.cpp +++ b/eidos/eidos_class_Object.cpp @@ -709,14 +709,18 @@ EidosValue_SP EidosClass::ExecuteMethod_size_length(EidosGlobalStringID p_method return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(p_target->Count())); } -EidosValue_SP EidosClass::GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) const +EidosValue_SP EidosClass::GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size) const { +#pragma unused (p_property_id, p_targets, p_targets_size) + // This is the backstop, called by subclasses EIDOS_TERMINATION << "ERROR (EidosObject::GetProperty_NO_SIGNATURE): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ClassNameForDisplay() << "." << EidosTerminate(nullptr); } -void EidosClass::SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_value) const +void EidosClass::SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size, const EidosValue &p_value) const { +#pragma unused (p_property_id, p_targets, p_targets_size, p_value) + // This is the backstop, called by subclasses EIDOS_TERMINATION << "ERROR (EidosObject::SetProperty_NO_SIGNATURE): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ClassNameForDisplay() << "." << EidosTerminate(nullptr); } From 4341dfa7e1ff998da5dcdc4c302b76370c98a3a1 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 5 Jul 2025 17:13:10 +0200 Subject: [PATCH 003/107] shift MutationType's API and internals to multitrait --- QtSLiM/QtSLiMChromosomeWidget.cpp | 2 +- QtSLiM/QtSLiMChromosomeWidget_GL.cpp | 5 +- QtSLiM/QtSLiMChromosomeWidget_QT.cpp | 5 +- QtSLiM/QtSLiMTablesDrawer.cpp | 30 +- QtSLiM/QtSLiMWindow.cpp | 40 +++ QtSLiM/help/SLiMHelpCallbacks.html | 2 +- QtSLiM/help/SLiMHelpClasses.html | 65 ++--- SLiMgui/ChromosomeView.mm | 7 +- SLiMgui/CocoaExtra.mm | 7 +- SLiMgui/SLiMHelpCallbacks.rtf | 2 +- SLiMgui/SLiMHelpClasses.rtf | 378 ++++++++---------------- SLiMgui/SLiMWindowController.mm | 21 +- VERSIONS | 43 +-- core/chromosome.cpp | 8 +- core/genomic_element_type.cpp | 3 +- core/haplosome.cpp | 12 +- core/individual.cpp | 4 +- core/mutation_type.cpp | 411 +++++++++++++++++++-------- core/mutation_type.h | 34 ++- core/slim_functions.cpp | 2 +- core/slim_globals.cpp | 10 +- core/slim_globals.h | 20 +- core/slim_test_genetics.cpp | 150 +++++----- core/species.cpp | 82 +++++- core/species.h | 4 + core/species_eidos.cpp | 5 +- eidos/eidos_globals.h | 2 +- eidos/eidos_value.cpp | 1 + 28 files changed, 763 insertions(+), 592 deletions(-) diff --git a/QtSLiM/QtSLiMChromosomeWidget.cpp b/QtSLiM/QtSLiMChromosomeWidget.cpp index e5f09503..2d6cd543 100644 --- a/QtSLiM/QtSLiMChromosomeWidget.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget.cpp @@ -480,7 +480,7 @@ void QtSLiMChromosomeWidgetController::runChromosomeContextMenuAtPoint(QPoint p_ MutationType *muttype = muttype_iter.second; slim_objectid_t muttype_id = muttype->mutation_type_id_; - if ((muttype->dfe_type_ != DFEType::kFixed) || (muttype->dfe_parameters_[0] != 0.0)) + if (muttype->IsPureNeutralDFE()) displayMuttypes_.emplace_back(muttype_id); } } diff --git a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp index 6117ba6e..b156bee2 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp @@ -254,6 +254,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch for (auto mutationTypeIter : mut_types) { MutationType *mut_type = mutationTypeIter.second; + EffectDistributionInfo &ed_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT if (mut_type->mutation_type_displayed_) { @@ -262,9 +263,9 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch bool mut_type_fixed_color = !mut_type->color_.empty(); // We optimize fixed-DFE mutation types only, and those using a fixed color set by the user - if ((mut_type->dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) + if ((ed_info.dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) { - slim_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(mut_type->dfe_parameters_[0])); + slim_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(ed_info.dfe_parameters_[0])); EIDOS_BZERO(heightBuffer, static_cast(displayPixelWidth) * sizeof(int16_t)); diff --git a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp index 5dcfc451..03231afd 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp @@ -256,11 +256,12 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch if (draw_muttypes_sequentially) { bool mut_type_fixed_color = !mut_type->color_.empty(); + EffectDistributionInfo &ed_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT // We optimize fixed-DFE mutation types only, and those using a fixed color set by the user - if ((mut_type->dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) + if ((ed_info.dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) { - slim_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(mut_type->dfe_parameters_[0])); + slim_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(ed_info.dfe_parameters_[0])); EIDOS_BZERO(heightBuffer, static_cast(displayPixelWidth) * sizeof(int16_t)); diff --git a/QtSLiM/QtSLiMTablesDrawer.cpp b/QtSLiM/QtSLiMTablesDrawer.cpp index 12c8a502..35589d15 100644 --- a/QtSLiM/QtSLiMTablesDrawer.cpp +++ b/QtSLiM/QtSLiMTablesDrawer.cpp @@ -87,8 +87,9 @@ static QImage imageForMutationOrInteractionType(MutationType *mut_type, Interact // so we run the sampling inside a try/catch block; if we get a raise, we just show a "?" in the plot. static bool rng_initialized = false; static Eidos_RNG_State local_rng; - - sample_size = (mut_type->dfe_type_ == DFEType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast + EffectDistributionInfo &ed_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + + sample_size = (ed_info.dfe_type_ == DFEType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast draws.reserve(sample_size); if (!rng_initialized) @@ -99,7 +100,7 @@ static QImage imageForMutationOrInteractionType(MutationType *mut_type, Interact _Eidos_SetOneRNGSeed(local_rng, 10); // arbitrary seed, but the same seed every time - std::swap(local_rng, gEidos_RNG_SINGLE); // swap in our local RNG for DrawSelectionCoefficient() + std::swap(local_rng, gEidos_RNG_SINGLE); // swap in our local RNG for DrawEffectForTrait() //std::clock_t start = std::clock(); @@ -107,7 +108,7 @@ static QImage imageForMutationOrInteractionType(MutationType *mut_type, Interact { for (size_t sample_count = 0; sample_count < sample_size; ++sample_count) { - double draw = mut_type->DrawSelectionCoefficient(); + double draw = mut_type->DrawEffectForTrait(0); // FIXME MULTITRAIT draws.emplace_back(draw); @@ -540,6 +541,7 @@ QVariant QtSLiMMutTypeTableModel::data(const QModelIndex &p_index, int role) con std::advance(mutTypeIter, p_index.row()); slim_objectid_t mutTypeID = mutTypeIter->first; MutationType *mutationType = mutTypeIter->second; + EffectDistributionInfo &ed_info = mutationType->effect_distributions_[0]; // FIXME MULTITRAIT if (p_index.column() == 0) { @@ -552,11 +554,11 @@ QVariant QtSLiMMutTypeTableModel::data(const QModelIndex &p_index, int role) con } else if (p_index.column() == 1) { - return QVariant(QString("%1").arg(static_cast(mutationType->default_dominance_coeff_), 0, 'f', 3)); + return QVariant(QString("%1").arg(static_cast(ed_info.default_dominance_coeff_), 0, 'f', 3)); } else if (p_index.column() == 2) { - switch (mutationType->dfe_type_) + switch (ed_info.dfe_type_) { case DFEType::kFixed: return QVariant(QString("fixed")); case DFEType::kGamma: return QVariant(QString("gamma")); @@ -571,27 +573,27 @@ QVariant QtSLiMMutTypeTableModel::data(const QModelIndex &p_index, int role) con { QString paramString; - if (mutationType->dfe_type_ == DFEType::kScript) + if (ed_info.dfe_type_ == DFEType::kScript) { // DFE type 's' has parameters of type string - for (unsigned int paramIndex = 0; paramIndex < mutationType->dfe_strings_.size(); ++paramIndex) + for (unsigned int paramIndex = 0; paramIndex < ed_info.dfe_strings_.size(); ++paramIndex) { - QString dfe_string = QString::fromStdString(mutationType->dfe_strings_[paramIndex]); + QString dfe_string = QString::fromStdString(ed_info.dfe_strings_[paramIndex]); paramString += ("\"" + dfe_string + "\""); - if (paramIndex < mutationType->dfe_strings_.size() - 1) + if (paramIndex < ed_info.dfe_strings_.size() - 1) paramString += ", "; } } else { // All other DFEs have parameters of type double - for (unsigned int paramIndex = 0; paramIndex < mutationType->dfe_parameters_.size(); ++paramIndex) + for (unsigned int paramIndex = 0; paramIndex < ed_info.dfe_parameters_.size(); ++paramIndex) { QString paramSymbol; - switch (mutationType->dfe_type_) + switch (ed_info.dfe_type_) { case DFEType::kFixed: paramSymbol = "s"; break; case DFEType::kGamma: paramSymbol = (paramIndex == 0 ? "s̄" : "α"); break; @@ -602,9 +604,9 @@ QVariant QtSLiMMutTypeTableModel::data(const QModelIndex &p_index, int role) con case DFEType::kScript: break; } - paramString += QString("%1=%2").arg(paramSymbol).arg(mutationType->dfe_parameters_[paramIndex], 0, 'f', 3); + paramString += QString("%1=%2").arg(paramSymbol).arg(ed_info.dfe_parameters_[paramIndex], 0, 'f', 3); - if (paramIndex < mutationType->dfe_parameters_.size() - 1) + if (paramIndex < ed_info.dfe_parameters_.size() - 1) paramString += ", "; } } diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index 9ac6fde2..4d4727cb 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -1754,6 +1754,21 @@ bool QtSLiMWindow::checkTerminationForAutofix(QString terminationMessage) beforeSelection4.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 4); QString beforeSelection4String = beforeSelection4.selectedText(); + // get the one character after the selected error range, to recognize if the error is followed by "(" + QTextCursor afterSelection1 = selection; + afterSelection1.setPosition(afterSelection1.selectionEnd(), QTextCursor::MoveAnchor); + afterSelection1.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1); + QString afterSelection1String = afterSelection1.selectedText(); + + QTextCursor selectionPlus1After = selection; + selectionPlus1After.setPosition(selectionPlus1After.selectionStart(), QTextCursor::MoveAnchor); + selectionPlus1After.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, selection.selectionEnd() - selection.selectionStart() + 1); + QString selectionPlus1AfterString = selectionPlus1After.selectedText(); + + //qDebug() << "selectionString ==" << selectionString; + //qDebug() << "afterSelection1String ==" << afterSelection1String; + //qDebug() << "selectionPlus1AfterString ==" << selectionPlus1AfterString; + // // Changes for SLiM 4.0: multispecies SLiM, mostly, plus fitness() -> mutationEffect() and fitness(NULL) -> fitnessEffect() // @@ -2114,6 +2129,31 @@ bool QtSLiMWindow::checkTerminationForAutofix(QString terminationMessage) (selectionString == "outputVCF")) return offerAndExecuteAutofix(selection, "outputHaplosomesToVCF", "The `outputVCF()` method of Haplosome has been renamed to `outputHaplosomesToVCF()`.", terminationMessage); + // + // Shift from one trait to multitrait for SLiM 5.1 + // + + if (terminationMessage.contains("property dominanceCoeff is not defined for object element type MutationType") && + (selectionString == "dominanceCoeff")) + return offerAndExecuteAutofix(selection, "defaultDominanceForTrait()", "The `dominanceCoeff` property of MutationType has become the method `defaultDominanceForTrait()`.", terminationMessage); + + if (terminationMessage.contains("property distributionType is not defined for object element type MutationType") && + (selectionString == "distributionType")) + return offerAndExecuteAutofix(selection, "distributionTypeForTrait()", "The `distributionType` property of MutationType has become the method `distributionTypeForTrait()`.", terminationMessage); + + if (terminationMessage.contains("property distributionParams is not defined for object element type MutationType") && + (selectionString == "distributionParams")) + return offerAndExecuteAutofix(selection, "distributionParamsForTrait()", "The `distributionParams` property of MutationType has become the method `distributionParamsForTrait()`.", terminationMessage); + + if ((afterSelection1String == "(") && + terminationMessage.contains("method setDistribution() is not defined on object element type MutationType") && + (selectionPlus1AfterString == "setDistribution(")) + return offerAndExecuteAutofix(selectionPlus1After, "setDistributionForTrait(NULL, ", "The `setDistribution()` method of MutationType has become the method `setDistributionForTrait()`.", terminationMessage); + + if (terminationMessage.contains("method drawSelectionCoefficient() is not defined on object element type MutationType") && + (selectionString == "drawSelectionCoefficient")) + return offerAndExecuteAutofix(selection, "drawEffectForTrait", "The `drawSelectionCoefficient()` method of MutationType has become the method `drawEffectForTrait()`.", terminationMessage); + return false; } diff --git a/QtSLiM/help/SLiMHelpCallbacks.html b/QtSLiM/help/SLiMHelpCallbacks.html index 8d5837f8..e575a8f8 100644 --- a/QtSLiM/help/SLiMHelpCallbacks.html +++ b/QtSLiM/help/SLiMHelpCallbacks.html @@ -112,7 +112,7 @@ if (homozygous)
return 1.0 + mut.selectionCoeff;
else
- return 1.0 + mut.mutationType.dominanceCoeff * mut.selectionCoeff;
+ return 1.0 + mut.dominanceCoeff * mut.selectionCoeff;
}

As mentioned above, a relative fitness of 1.0 is neutral (whereas a selection coefficient of 0.0 is neutral); the 1.0 + in these calculations converts between the selection coefficient scale and the relative fitness scale, and is therefore essential.  However, the effect global variable mentioned above would already contain this value, precomputed by SLiM, so you could simply return effect to get that behavior when you want it:

mutationEffect(m1) {
diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 03ee71cc..5a78f8cf 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -19,10 +19,8 @@ p.p10 {margin: 3.0px 0.0px 3.0px 27.4px; font: 9.0px Menlo; color: #000000} p.p11 {margin: 18.0px 0.0px 3.0px 0.0px; font: 11.0px Optima; color: #000000} p.p12 {margin: 6.0px 0.0px 3.0px 0.0px; font: 11.0px Optima; color: #000000} - p.p13 {margin: 3.0px 0.0px 3.0px 27.4px; font: 11.0px Optima} - p.p14 {margin: 3.0px 0.0px 3.0px 27.4px; font: 11.0px Optima; color: #000000} - p.p15 {margin: 2.0px 0.0px 2.0px 0.0px; text-indent: 13.7px; font: 11.0px 'Times New Roman'; min-height: 12.0px} - p.p16 {margin: 3.0px 0.0px 3.0px 27.4px; font: 9.0px Menlo} + p.p13 {margin: 2.0px 0.0px 2.0px 0.0px; text-indent: 13.7px; font: 11.0px 'Times New Roman'; min-height: 12.0px} + p.p14 {margin: 3.0px 0.0px 3.0px 27.4px; font: 9.0px Menlo} span.s1 {font: 9.0px Menlo} span.s2 {font: 10.0px 'Times New Roman'} span.s3 {font-kerning: none} @@ -37,11 +35,9 @@ span.s12 {text-decoration: underline ; color: #0000ff} span.s13 {font: 6.7px 'Times New Roman'} span.s14 {font: 10.0px 'Lucida Grande'} - span.s15 {font: 11.0px 'Times New Roman'} - span.s16 {font: 11.0px Helvetica} - span.s17 {font: 6.7px Optima} - span.s18 {font: 10.0px Optima; color: #000000} - span.s19 {font: 6.7px Optima; font-kerning: none} + span.s15 {font: 6.7px Optima} + span.s16 {font: 10.0px Optima; color: #000000} + span.s17 {font: 6.7px Optima; font-kerning: none} span.Apple-tab-span {white-space:pre} @@ -715,22 +711,6 @@

In WF models this property is T by default, since conversion to Substitution objects provides large speed benefits; it should be set to F only if necessary, and only on the mutation types for which it is necessary.  This might be needed, for example, if you are using a mutationEffect() callback to implement an epistatic relationship between mutations; a mutation epistatically influencing the fitness of other mutations through a mutationEffect() callback would need to continue having that influence even after reaching fixation, but if the simulation were to replace the fixed mutation with a Substitution object the mutation would no longer be considered in fitness calculations (unless the callback explicitly consulted the list of Substitution objects kept by the simulation).  Other script-defined behaviors in mutationEffect(), interaction(), mateChoice(), modifyChild(), and recombination() callbacks might also necessitate the disabling of substitution for a given mutation type; this is an important consideration to keep in mind.

In contrast, for nonWF models this property is F by default, because even mutations with no epistatis or other indirect fitness effects will continue to influence the survival probabilities of individuals.  For nonWF models, only neutral mutation types with no epistasis or other side effects can safely be converted to substitutions upon fixation.  When such a pure-neutral mutation type is defined in a nonWF model, this property should be set to T to tell SLiM that substitution is allowed; this may have very large positive effects on performance, so it is important to remember when modeling background neutral mutations.

SLiM consults this flag at the end of each tick when deciding whether to substitute each fixed mutation.  If this flag is T, all eligible fixed mutations will be converted at the end of the current tick, even if they were previously left unconverted because of the previous value of the flag.  Setting this flag to F will prevent future substitutions, but will not cause any existing Substitution objects to be converted back into Mutation objects.

-

distributionParams => (fs)

-

The parameters that configure the chosen distribution of fitness effects.  This will be of type string for DFE type "s", and type float for all other DFE types.

-

distributionType => (string$)

-

The type of distribution of fitness effects; one of "f", "g", "e", "n", "w", or "s":

-

"f" – A fixed fitness effect.  This DFE type has a single parameter, the selection coefficient s to be used by all mutations of the mutation type.

-

"g" – A gamma-distributed fitness effect.  This DFE type is specified by two parameters, a shape parameter and a mean value.  The gamma distribution from which mutations are drawn is given by the probability density function P(s | α,β= [Γ(α)βα]−1exp(−s/β), where α is the shape parameter, and the specified mean for the distribution is equal to αβ.  Note that this parameterization is the same as for the Eidos function rgamma().  A gamma distribution is often used to model deleterious mutations at functional sites.

-

"e" – An exponentially-distributed fitness effect.  This DFE type is specified by a single parameter, the mean of the distribution.  The exponential distribution from which mutations are drawn is given by the probability density function P(s | β) = β−1exp(−s/β), where β is the specified mean for the distribution.  This parameterization is the same as for the Eidos function rexp().  An exponential distribution is often used to model beneficial mutations.

-

"n" – A normally-distributed fitness effect.  This DFE type is specified by two parameters, a mean and a standard deviation.  The normal distribution from which mutations are drawn is given by the probability density function P(s | μ,σ) = (2πσ2)−1/2exp(−(sμ)2/2σ2), where μ is the mean and σ is the standard deviation.  This parameterization is the same as for the Eidos function rnorm().  A normal distribution is often used to model mutations that can be either beneficial or deleterious, since both tails of the distribution are unbounded.

-

"p" – A Laplace-distributed fitness effect.  This DFE type is specified by two parameters, a mean and a scale.  The Laplace distribution from which mutations are drawn is given by the probability density function P(s | μ,b) = exp(−|sμ|/b)/2b, where μ is the mean and b is the scale parameter.  A Laplace distribution is sometimes used to model a mix of both deleterious and beneficial mutations.

-

"w" – A Weibull-distributed fitness effect.  This DFE type is specified by a scale parameter and a shape parameter.  The Weibull distribution from which mutations are drawn is given by the probability density function P(s | λ,k) = (k/λk)sk−1exp(−(s/λ)k), where λ is the scale parameter and k is the shape parameter.  This parameterization is the same as for the Eidos function rweibull().  A Weibull distribution is often used to model mutations following extreme-value theory.

-

"s" – A script-based fitness effect.  This DFE type is specified by a script parameter of type string, specifying an Eidos script to be executed to produce each new selection coefficient.  For example, the script "return rbinom(1);" could be used to generate selection coefficients drawn from a binomial distribution, using the Eidos function rbinom(), even though that mutational distribution is not supported by SLiM directly.  The script must return a singleton float or integer.

-

Note that these distributions can in principle produce selection coefficients smaller than -1.0. In that case, the mutations will be evaluated as “lethal” by SLiM, and the relative fitness of the individual will be set to 0.0.

-

dominanceCoeff <–> (float$)

-

The default dominance coefficient used for mutations of this type when heterozygous.  This default value is taken by new mutations of this mutation type when they are created, as the value of their dominanceCoeff property, but that can be changed later with the Mutation method setDominanceCoeff().

-

Note that the dominance coefficient is not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

-

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation type muttype’s dominance coefficient to some number x, muttype.dominanceCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutation types.

hemizygousDominanceCoeff <–> (float$)

The dominance coefficient used for mutations of this type when they occur opposite a null haplosome (as can occur in sex-chromosome models and models involving a mix of haploids and diploids).  This defaults to 1.0, and is used only in models where null haplosomes are present; the dominanceCoeff property of the mutation is the dominance coefficient used in most circumstances.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

As with the dominanceCoeff property, this is stored internally using a single-precision float; see the documentation for dominanceCoeff for discussion.

@@ -751,10 +731,21 @@

tag <–> (integer$)

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.  See also the getValue() and setValue() methods (provided by the Dictionary class; see the Eidos manual), for another way of attaching state to mutation types.

5.11.2  MutationType methods

-

– (float)drawSelectionCoefficient([integer$ n = 1])

-

Draws and returns a vector of n selection coefficients using the currently defined distribution of fitness effects (DFE) for the target mutation type.  If the DFE is type "s", this method will result in synchronous execution of the DFE’s script.

-

– (void)setDistribution(string$ distributionType, ...)

-

Set the distribution of fitness effects for a mutation type.  The distributionType may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for the exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  The DFE for a mutation type is normally a constant in simulations, so be sure you know what you are doing.

+

– (float$)defaultDominanceForTrait([Nio<Trait> trait = NULL])

+

Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their dominanceCoeff property, but that can be changed later with the Mutation method setDominanceCoeff().

+

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

+

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

+

– (fs)distributionParamsForTrait([Nio<Trait> trait = NULL])

+

Returns the parameters that configure the distribution of effects for the specified trait or traits.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DFE type "s", and type float for all other DFE types.

+

– (string$)distributionTypeForTrait([Nio<Trait> trait = NULL])

+

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

+

– (float)drawEffectForTrait([Nio<Trait> trait = NULL], [integer$ n = 1])

+

Draws and returns a vector of n mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  See the MutationType class documentation for discussion of the supported distributions and their uses.  If the distribution of effects is of type "s", this method will result in synchronous execution of the script associated with the distribution of effects.

+

– (void)setDefaultDominanceForTrait(Nio<Trait> trait, float dominance)

+

Set the default dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of defaultDominance must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of defaultDominance is used for each corresponding trait).

+

– (void)setDistributionForTrait(Nio<Trait> trait, string$ distributionType, ...)

+

Set the distribution of effects for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.

+

The distributionType may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for the exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  See the MutationType class documentation for discussion of these distributions and their uses.  The distribution of effects for a mutation type is normally a constant in simulations, so be sure you know what you are doing.

5.12  Class Plot

5.12.1  Plot properties

title => (string$)

@@ -811,7 +802,7 @@

type => (string$)

The type of the script block; this will be "first", "early", or "late" for the three types of Eidos events, or "initialize", "fitnessEffect", "interaction", "mateChoice", "modifyChild", "mutation", "mutationEffect", "recombination", "reproduction", or "survival" for the respective types of Eidos callbacks.

5.13.2  SLiMEidosBlock methods

-


+


5.14  Class SLiMgui

5.14.1  SLiMgui properties

pid => (integer$)

@@ -861,7 +852,7 @@

– (object<SpatialMap>$)divide(ifo<SpatialMap> x)

Divides the spatial map by x.  One possibility is that x is a singleton integer or float value; in this case, each grid value of the target spatial map is divided by x.  Another possibility is that x is an integer or float vector/matrix/array of the same dimensions as the target spatial map’s grid; in this case, each grid value of the target spatial map is divided by the corresponding value of x.  The third possibility is that x is itself a (singleton) spatial map; in this case, each grid value of the target spatial map is divided by the corresponding grid value of x (and thus the two spatial maps must match in their spatiality, their spatial bounds, and their grid dimensions).  The target spatial map is returned, to allow easy chaining of operations.

– (object<SpatialMap>$)exp(void)

-

Exponentiates the values of the spatial map.  More precisely, each grid value x of the target spatial map is exponentiated – replaced by the value ex.  The target spatial map is returned, to allow easy chaining of operations.

+

Exponentiates the values of the spatial map.  More precisely, each grid value x of the target spatial map is exponentiated – replaced by the value ex.  The target spatial map is returned, to allow easy chaining of operations.

– (float)gridValues(void)

Returns the values for the spatial map’s grid as a vector (for a 1D map), a matrix (for a 2D map), or an array (for a 3D map).  The form and orientation of the returned values is such that it could be used to create a new spatial map, with defineSpatialMap(), which would be identical to the original.

– (object<SpatialMap>$)interpolate(integer$ factor, [string$ method = "linear"])

@@ -994,10 +985,10 @@

Note that this method is only for use in nonWF models, in which mortality is managed manually by the model script.  In WF models, mortality is managed automatically by the SLiM core when the new offspring generation becomes the parental generation and the previous parental generation dies; mortality does not otherwise occur in WF models.  In nonWF models, mortality normally occurs during the survival stage of the tick cycle, based upon the fitness values calculated by SLiM, and survival() callbacks can influence the outcome of that survival stage.  Calls to killIndividuals(), on the other hand, can be made at any time during first(), early(), or late() events, and the result cannot be modified by survival() callbacks; the given individuals are simply immediately killed.  This method therefore provides an alternative, and relatively rarely used, mortality mechanism that is disconnected from fitness.

– (integer)mutationCounts(Nio<Subpopulation> subpops, [No<Mutation> mutations = NULL])

Return an integer vector with the frequency counts of all of the Mutation objects passed in mutations, within the Subpopulation objects in subpops.  The subpops argument is required, but you may pass NULL to get population-wide frequency counts.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects.  If the optional mutations argument is NULL (the default), frequency counts will be returned for all of the active Mutation objects in the species – the same Mutation objects, and in the same order, as would be returned by the mutations property of sim, in other words.

-

See the -mutationFrequencies() method to obtain float frequencies instead of integer counts.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

+

See the -mutationFrequencies() method to obtain float frequencies instead of integer counts.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

– (float)mutationFrequencies(Nio<Subpopulation> subpops, [No<Mutation> mutations = NULL])

Return a float vector with the frequencies of all of the Mutation objects passed in mutations, within the Subpopulation objects in subpops.  The subpops argument is required, but you may pass NULL to get population-wide frequencies.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects.  If the optional mutations argument is NULL (the default), frequencies will be returned for all of the active Mutation objects in the species – the same Mutation objects, and in the same order, as would be returned by the mutations property of sim, in other words.

-

See the -mutationCounts() method to obtain integer counts instead of float frequencies.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

+

See the -mutationCounts() method to obtain integer counts instead of float frequencies.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

 (object<Mutation>)mutationsOfType(io<MutationType>$ mutType)

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations that are currently active in the species.  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType().  This method is often used to look up an introduced mutation at a later point in the simulation, since there is no way to keep persistent references to objects in SLiM.  This method is provided for speed; it is much faster than the corresponding Eidos code.

– (void)outputFixedMutations([Ns$ filePath = NULL], [logical$ append = F], [logical$ objectTags = F])

@@ -1010,7 +1001,7 @@

Output the state of the entire population.  If the optional parameter filePath is NULL (the default), output will be sent to Eidos’s output stream.  Otherwise, output will be sent to the filesystem path specified by filePath, overwriting that file if append if F, or appending to the end of it if append is T.  When writing to a file, a logical flag, binary, may be supplied as well.  If binary is T, the population state will be written as a binary file instead of a text file (binary data cannot be written to the standard output stream).  The binary file is usually smaller, and in any case will be read much faster than the corresponding text file would be read.  Binary files are not guaranteed to be portable between platforms; in other words, a binary file written on one machine may not be readable on a different machine (but in practice it usually will be, unless the platforms being used are fairly unusual).  If binary is F (the default), a text file will be written.

Beginning with SLiM 2.3, the spatialPositions parameter may be used to control the output of the spatial positions of individuals in species for which continuous space has been enabled using the dimensionality option of initializeSLiMOptions().  If spatialPositions is F, the output will not contain spatial positions, and will be identical to the output generated by SLiM 2.1 and later.  If spatialPositions is T, spatial position information will be output if it is available.  If the species does not have continuous space enabled, the spatialPositions parameter will be ignored.  Positional information may be output for all output destinations – the Eidos output stream, a text file, or a binary file.

Beginning with SLiM 3.0, the ages parameter may be used to control the output of the ages of individuals in nonWF simulations.  If ages is F, the output will not contain ages, preserving backward compatibility with the output format of SLiM 2.1 and later.  If ages is T, ages will be output for nonWF models.  In WF simulations, the ages parameter will be ignored.

-

Beginning with SLiM 3.3, the ancestralNucleotides parameter may be used to control the output of the ancestral nucleotide sequence in nucleotide-based models.  If ancestralNucleotides is F, the output will not contain ancestral nucleotide information, and so the ancestral sequence will not be restored correctly if the saved file is loaded with readPopulationFile().  This option is provided because the ancestral sequence may be quite large, for models with a long chromosome (e.g., 1 GB if the chromosome is 109 bases long, when saved in text format, or 0.25 GB when saved in binary format).  If the model is not nucleotide-based (as enabled with the nucleotideBased parameter to initializeSLiMOptions()), the ancestralNucleotides parameter will be ignored.  Note that in nucleotide-based models the output format will always include the nucleotides associated with any nucleotide-based mutations; the ancestralNucleotides flag governs only the ancestral sequence.

+

Beginning with SLiM 3.3, the ancestralNucleotides parameter may be used to control the output of the ancestral nucleotide sequence in nucleotide-based models.  If ancestralNucleotides is F, the output will not contain ancestral nucleotide information, and so the ancestral sequence will not be restored correctly if the saved file is loaded with readPopulationFile().  This option is provided because the ancestral sequence may be quite large, for models with a long chromosome (e.g., 1 GB if the chromosome is 109 bases long, when saved in text format, or 0.25 GB when saved in binary format).  If the model is not nucleotide-based (as enabled with the nucleotideBased parameter to initializeSLiMOptions()), the ancestralNucleotides parameter will be ignored.  Note that in nucleotide-based models the output format will always include the nucleotides associated with any nucleotide-based mutations; the ancestralNucleotides flag governs only the ancestral sequence.

Beginning with SLiM 3.5, the pedigreeIDs parameter may be used to request that pedigree IDs be written out (and read in by readFromPopulationFile(), subsequently).  This option is turned off (F) by default, for brevity.  This option may only be used if SLiM’s optional pedigree tracking has been enabled with initializeSLiMOptions(keepPedigrees=T).

Beginning with SLiM 5.0, the objectTags parameter may be used to request that tag values for objects be written out.  This option is turned off (F) by default, for brevity; if it turned on (T), the values of all tags for all objects of supported classes (Chromosome, Subpopulation, Individual, Haplosome, Mutation, Substitution) will be written.  For individuals, the tag, tagF, tagL0, tagL1, tagL2, tagL3, and tagL4 properties will be written; for chromosomes, subpopulations, haplosomes, and mutations, the tag property will be written.  The saved tag information can be read in by readFromPopulationFile(), but only if the output is in binary format (binary=T).  Note that if there is other state that you wish you persist, such as tags on objects of other classes, values attached to objects with setValue(), and so forth, you should persist that state in separate files using calls such as writeFile().

Beginning with SLiM 5.0, the substitutions parameter may be used to request that information about Substitution objects in the simulation be written out.  This option is turned off (F) by default, for brevity.  The saved substitution information can be read in by readFromPopulationFile(), but only if the output is in binary format (binary=T).

@@ -1238,7 +1229,7 @@

Returns a vector containing n points that are derived from point by adding a deviation drawn from a dispersal kernel (specified by maxDistance, functionType, and the ellipsis parameters ..., as detailed below) and then applying a boundary condition specified by boundary.  This method therefore performs the steps of a simple dispersal algorithm in a single vectorized call.  See deviatePositions() for an even more efficient approach.

The parameter point may contain a single point which is deviated and bounded n independent times, or may contain n points each of which is deviated and bounded.  In any case, each point in point should match the dimensionality of the model – one element in a 1D model, two elements in a 2D model, or three elements in a 3D model.  This method should not be called in a non-spatial model.

The dispersal kernel is specified similarly to other kernel-based methods, such as setInteractionFunction() and smooth().  For pointDeviated(), functionType may be "f" with no ellipsis arguments ... to use a flat kernel out to maxDistance; "l" with no ellipsis arguments for a kernel that decreases linearly from the center to zero at maxDistance; "e", in which case the ellipsis should supply a numeric$ lambda (rate) parameter for a negative exponential function; "n", in which case the ellipsis should supply a numeric$ sigma (standard deviation) parameter for a Gaussian function; or "t", in which case the ellipsis should supply a numeric$ degrees of freedom and a numeric$ scale parameter for a t-distribution function.  The Cauchy ("c") kernel is not supported by pointDeviated() since it is not well-behaved for this purpose, and the Student’s t ("t") kernel is not allowed in 3D models at present simply because it hasn’t been implemented.  See the InteractionType class documentation for more detailed discussion of the available kernel types and their parameters and probability distribution functions.  For pointDeviated(), the ellipsis parameters that follow functionType may each, independently, be either a singleton or a vector of length equal to n.  This allows each point to be deviated with a different kernel, representing, for example, the movements of individuals with differing dispersal capabilities/propensities.  (However, other parameters such as boundary, maxDistance, and functionType must be the same for all of the points, in the present design.)

-

The random points returned from this method are drawn from the probability distribution that is radially symmetric and has density proportional to the kernel – in other words, at distance r the density is proportional to the kernel type referred to by functionType.  (Said another way, the shape of the cross-section through the probability density function is given by the kernel.)  For instance, the value of the type "e" (exponential) kernel with rate a at r is proportional to exp(−ar), and so in 2D, the probability density that this method with kernel type "e" draws from has density proportional to p(xy) = exp(−a sqrt(x2 + y2)), since r = sqrt(x2 + y2) is the distance.  Note that the distribution of the distance is not given by the kernel except in 1D: in the type "e" example, the distribution of the distance in 1D is exponential, while in 2D it has density proportional to r exp(−ar) (i.e., Gamma with shape parameter 1).  For another example, the value of the type "n" (Normal) kernel at r with standard deviation 1 is proportional to exp(−r2 / 2), and so the density is proportional to p(xy) = exp(−(x2 + y2) / 2).  This is the standard bivariate Normal, and equivalent to drawing independent Normals for the x and y directions; however, the Normal is the only distribution for which independent draws along each axis will result in a radially symmetric distribution.  The distribution of the distance in 2D with type "n" is proportional to r exp(−r2 / 2), i.e., Rayleigh.

+

The random points returned from this method are drawn from the probability distribution that is radially symmetric and has density proportional to the kernel – in other words, at distance r the density is proportional to the kernel type referred to by functionType.  (Said another way, the shape of the cross-section through the probability density function is given by the kernel.)  For instance, the value of the type "e" (exponential) kernel with rate a at r is proportional to exp(−ar), and so in 2D, the probability density that this method with kernel type "e" draws from has density proportional to p(xy) = exp(−a sqrt(x2 + y2)), since r = sqrt(x2 + y2) is the distance.  Note that the distribution of the distance is not given by the kernel except in 1D: in the type "e" example, the distribution of the distance in 1D is exponential, while in 2D it has density proportional to r exp(−ar) (i.e., Gamma with shape parameter 1).  For another example, the value of the type "n" (Normal) kernel at r with standard deviation 1 is proportional to exp(−r2 / 2), and so the density is proportional to p(xy) = exp(−(x2 + y2) / 2).  This is the standard bivariate Normal, and equivalent to drawing independent Normals for the x and y directions; however, the Normal is the only distribution for which independent draws along each axis will result in a radially symmetric distribution.  The distribution of the distance in 2D with type "n" is proportional to r exp(−r2 / 2), i.e., Rayleigh.

The boundary condition must be one of "none", "periodic", "reflecting", "stopping", or "reprising".  For "none", no boundary condition is enforced; the deviated points are simply returned as is.  For "periodic", "reflecting", and "stopping", the boundary condition is enforced just as it is by the pointPeriodic(), pointReflected(), and pointStopped() methods; see their documentation for further details.  For "reprising", if the deviated point is out of bounds a new deviated point will be chosen, based upon the same original point, until a point inside bounds is obtained.  Note that absorbing boundaries (for which being out-of-bounds is lethal) would need to be implemented in script; this method cannot enforce them.  (Note, however, that the deviatePositions() method of Subpopulation can enforce absorbing boundaries.)

Note that for the typical usage case, in which point comes from the spatialPosition property for a vector of individuals, and the result is then set back onto the same vector of individuals using the setSpatialPosition() method, the deviatePositions() method provides an even more efficient alternative.

– (logical)pointInBounds(float point)

@@ -1320,7 +1311,7 @@

tag <–> (integer$)

A user-defined integer value.  The value of tag is carried over automatically from the original Mutation object.  Apart from that, the value of tag is not used by SLiM; it is free for you to use.

5.18.2  Substitution methods

-


+


5.19  Class Trait

5.19.1  Trait properties

baselineOffset <–> (float$)

@@ -1342,6 +1333,6 @@

type => (string$)

The type of the trait, as a string.  In the present design, this will be either "multiplicative" or "additive".

5.19.2  Trait methods

-


+


diff --git a/SLiMgui/ChromosomeView.mm b/SLiMgui/ChromosomeView.mm index 7d779c4a..e492a883 100644 --- a/SLiMgui/ChromosomeView.mm +++ b/SLiMgui/ChromosomeView.mm @@ -701,11 +701,12 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome if (mut_type->mutation_type_displayed_) { bool mut_type_fixed_color = !mut_type->color_.empty(); + EffectDistributionInfo &ed_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT // We optimize fixed-DFE mutation types only, and those using a fixed color set by the user - if ((mut_type->dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) + if ((ed_info.dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) { - slim_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : (slim_selcoeff_t)mut_type->dfe_parameters_[0]); + slim_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : (slim_selcoeff_t)ed_info.dfe_parameters_[0]); EIDOS_BZERO(heightBuffer, displayPixelWidth * sizeof(int16_t)); @@ -1011,7 +1012,7 @@ - (IBAction)filterNonNeutral:(id)sender MutationType *muttype = muttype_iter.second; slim_objectid_t muttype_id = muttype->mutation_type_id_; - if ((muttype->dfe_type_ != DFEType::kFixed) || (muttype->dfe_parameters_[0] != 0.0)) + if (!muttype->IsPureNeutralDFE()) display_muttypes_.emplace_back(muttype_id); } diff --git a/SLiMgui/CocoaExtra.mm b/SLiMgui/CocoaExtra.mm index 79ec8f3d..9495a71e 100644 --- a/SLiMgui/CocoaExtra.mm +++ b/SLiMgui/CocoaExtra.mm @@ -555,8 +555,9 @@ - (void)drawRect:(NSRect)dirtyRect // so we run the sampling inside a try/catch block; if we get a raise, we just show a "?" in the plot. static bool rng_initialized = false; static Eidos_RNG_State local_rng; + EffectDistributionInfo &ed_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT - sample_size = (mut_type->dfe_type_ == DFEType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast + sample_size = (ed_info.dfe_type_ == DFEType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast draws.reserve(sample_size); if (!rng_initialized) @@ -569,7 +570,7 @@ - (void)drawRect:(NSRect)dirtyRect Eidos_RNG_State *slim_rng_state = EIDOS_STATE_RNG(omp_get_thread_num()); - std::swap(local_rng, *slim_rng_state); // swap in our local RNG for DrawSelectionCoefficient() + std::swap(local_rng, *slim_rng_state); // swap in our local RNG for DrawEffectForTrait() //std::clock_t start = std::clock(); @@ -577,7 +578,7 @@ - (void)drawRect:(NSRect)dirtyRect { for (int sample_count = 0; sample_count < sample_size; ++sample_count) { - double draw = mut_type->DrawSelectionCoefficient(); + double draw = mut_type->DrawEffectForTrait(0); // FIXME MULTITRAIT draws.emplace_back(draw); diff --git a/SLiMgui/SLiMHelpCallbacks.rtf b/SLiMgui/SLiMHelpCallbacks.rtf index 728492e8..25e66c44 100644 --- a/SLiMgui/SLiMHelpCallbacks.rtf +++ b/SLiMgui/SLiMHelpCallbacks.rtf @@ -438,7 +438,7 @@ In addition to the standard SLiM globals, a \f2\fs22 callback to compute a fitness value. To implement the standard fitness functions used by SLiM for an autosomal simulation with no null haplosomes involved, for example, you could do something like this:\ \pard\tx990\tx1260\tx1530\tx1800\tx2070\tx2340\tx2610\tx2880\tx3150\tx3420\pardeftab720\li547\ri720\sb180\sa180\partightenfactor0 -\f3\fs18 \cf2 mutationEffect(m1) \{\uc0\u8232 if (homozygous)\u8232 return 1.0 + mut.selectionCoeff;\u8232 else\u8232 return 1.0 + mut.mutationType.dominanceCoeff * mut.selectionCoeff;\u8232 \}\ +\f3\fs18 \cf2 mutationEffect(m1) \{\uc0\u8232 if (homozygous)\u8232 return 1.0 + mut.selectionCoeff;\u8232 else\u8232 return 1.0 + mut.dominanceCoeff * mut.selectionCoeff;\u8232 \}\ \pard\pardeftab397\fi274\ri720\sb40\sa40\partightenfactor0 \f2\fs22 \cf2 As mentioned above, a relative fitness of diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index acbb06da..cc28247c 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -1,8 +1,7 @@ {\rtf1\ansi\ansicpg1252\cocoartf2709 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Optima-Bold;\f1\fswiss\fcharset0 Optima-Italic;\f2\fnil\fcharset0 Menlo-Italic; \f3\fnil\fcharset0 Menlo-Regular;\f4\fswiss\fcharset0 Optima-Regular;\f5\froman\fcharset0 TimesNewRomanPSMT; -\f6\froman\fcharset0 TimesNewRomanPS-ItalicMT;\f7\fnil\fcharset0 LucidaGrande;\f8\fswiss\fcharset0 Helvetica-Oblique; -\f9\fswiss\fcharset0 Helvetica;} +\f6\froman\fcharset0 TimesNewRomanPS-ItalicMT;\f7\fnil\fcharset0 LucidaGrande;} {\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue255;} {\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\cssrgb\c0\c0\c100000;} \margl1440\margr1440\vieww9000\viewh19740\viewkind0 @@ -1803,7 +1802,6 @@ Beginning in SLiM 2.1, this is a class method, not an instance method. This mea \f4\fs20 method \f3\fs18 recalculateFitness() \f4\fs20 \'96 but see the documentation of that method for caveats.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf2 Note that in nonWF models that use tree-sequence recording, mutations cannot be added to an individual after the tick in which the individual is created (i.e., when the \f3\fs18 age \f4\fs20 of the individual is greater than @@ -1914,7 +1912,6 @@ Beginning in SLiM 2.1, this is a class method, not an instance method. This mea \f4\fs20 method \f3\fs18 recalculateFitness() \f4\fs20 \'96 but see the documentation of that method for caveats.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf2 Note that in nonWF models that use tree-sequence recording, mutations cannot be added to an individual after the tick in which the individual is created (i.e., when the \f3\fs18 age \f4\fs20 of the individual is greater than @@ -2572,8 +2569,9 @@ The \f4\fs20 subfield (or a generation of origin \f3\fs18 GO \f4\fs20 field, which was the SLiM convention before SLiM 4), the current tick will be used.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f3\fs18 REF +\f3\fs18 \cf2 REF \f4\fs20 and \f3\fs18 ALT \f4\fs20 must always be comprised of simple nucleotides ( @@ -5418,8 +5416,7 @@ See \f4\fs20 callback has changed in such a way that previously calculated interaction strengths are no longer correct, \f3\fs18 unevaluate() \f4\fs20 allows the interaction to begin again from scratch.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \expnd0\expndtw0\kerning0 +\expnd0\expndtw0\kerning0 In WF models, all interactions are automatically reset to an unevaluated state at the moment when the new offspring generation becomes the parental generation (at step 4 in the tick cycle).\ In nonWF models, all interactions are automatically reset to an unevaluated state twice per tick: immediately after \f3\fs18 reproduction() @@ -6240,236 +6237,7 @@ SLiM consults this flag at the end of each tick when deciding whether to substit \f4\fs20 objects.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 \kerning1\expnd0\expndtw0 distributionParams => (fs)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf0 The parameters that configure the chosen distribution of fitness effects. This will be of type -\f3\fs18 string -\f4\fs20 for DFE type -\f3\fs18 "s" -\f4\fs20 , and type -\f3\fs18 float -\f4\fs20 for all other DFE types. -\f5 \ -\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - -\f3\fs18 \cf0 distributionType => (string$)\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf0 The type of distribution of fitness effects; one of -\f3\fs18 "f" -\f4\fs20 , -\f3\fs18 "g" -\f5\fs20 , -\f4 -\f3\fs18 "e" -\f5\fs20 , -\f4 -\f3\fs18 "n" -\f5\fs20 , -\f4 -\f3\fs18 "w" -\f5\fs20 , -\f4 or -\f3\fs18 "s" -\f5\fs20 :\ - -\f3\fs18 "f" -\f4\fs22 \'96 A -\f0\b f -\f4\b0 ixed fitness effect. This DFE type has a single parameter, the selection coefficient -\f1\i s -\f4\i0 to be used by all mutations of the mutation type.\ - -\f3\fs18 "g" -\f4\fs22 \'96 A -\f0\b g -\f4\b0 amma-distributed fitness effect. This DFE type is specified by two parameters, a shape parameter and a mean value. The gamma distribution from which mutations are drawn is given by the probability density function -\f6\i P -\f5\i0 ( -\f6\i s -\f5\i0 \'a0|\'a0 -\f8\i \uc0\u945 -\f5\i0 , -\f8\i \uc0\u946 -\f5\i0 )\'a0 -\f9 = [\uc0\u915 ( -\f8\i \uc0\u945 -\f5\i0 ) -\f8\i \uc0\u946 \u945 -\f5\i0 ]\super \uc0\u8722 1\nosupersub exp(\uc0\u8722 -\f6\i s -\f5\i0 / -\f8\i \uc0\u946 -\f5\i0 ) -\f4 , where -\f8\i \uc0\u945 -\f4\i0 is the shape parameter, and the specified mean for the distribution is equal to -\f8\i \uc0\u945 \u946 -\f4\i0 . Note that this parameterization is the same as for the Eidos function -\f3\fs18 rgamma() -\f4\fs22 . A gamma distribution is often used to model deleterious mutations at functional sites.\ - -\f3\fs18 "e" -\f4\fs22 \'96 An -\f0\b e -\f4\b0 xponentially-distributed fitness effect. This DFE type is specified by a single parameter, the mean of the distribution. The exponential distribution from which mutations are drawn is given by the probability density function -\f6\i P -\f5\i0 ( -\f6\i s -\f5\i0 \'a0|\'a0 -\f8\i \uc0\u946 -\f5\i0 )\'a0= -\f8\i \uc0\u946 -\f5\i0 \super \uc0\u8722 1\nosupersub exp(\uc0\u8722 -\f6\i s -\f5\i0 / -\f8\i \uc0\u946 -\f5\i0 ) -\f4 , where -\f8\i \uc0\u946 -\f4\i0 is the specified mean for the distribution. This parameterization is the same as for the Eidos function -\f3\fs18 rexp() -\f4\fs22 . An exponential distribution is often used to model beneficial mutations.\ - -\f3\fs18 "n" -\f4\fs22 \'96 A -\f0\b n -\f4\b0 ormally-distributed fitness effect. This DFE type is specified by two parameters, a mean and a standard deviation. The normal distribution from which mutations are drawn is given by the probability density function -\f6\i P -\f5\i0 ( -\f6\i s -\f5\i0 \'a0|\'a0 -\f8\i \uc0\u956 -\f5\i0 , -\f8\i \uc0\u963 -\f5\i0 )\'a0= (2 -\f9 \uc0\u960 -\f8\i \uc0\u963 -\f5\i0 \super 2\nosupersub )\super \uc0\u8722 1/2\nosupersub exp(\uc0\u8722 ( -\f6\i s -\f5\i0 \uc0\u8722 -\f8\i \uc0\u956 -\f5\i0 )\super 2\nosupersub /2 -\f8\i \uc0\u963 -\f5\i0 \super 2\nosupersub ) -\f4 , where -\f8\i \uc0\u956 -\f4\i0 is the mean and -\f8\i \uc0\u963 -\f4\i0 is the standard deviation. This parameterization is the same as for the Eidos function -\f3\fs18 rnorm() -\f4\fs22 . A normal distribution is often used to model mutations that can be either beneficial or deleterious, since both tails of the distribution are unbounded.\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 - -\f3\fs18 \cf2 "p" -\f4\fs22 \'96 A La -\f0\b p -\f4\b0 lace-distributed fitness effect. This DFE type is specified by two parameters, a mean and a scale. The Laplace distribution from which mutations are drawn is given by the probability density function -\f6\i P -\f5\i0 ( -\f6\i s -\f5\i0 \'a0|\'a0 -\f6\i \uc0\u956 -\f5\i0 , -\f6\i b -\f5\i0 )\'a0= exp(\uc0\u8722 | -\f6\i s -\f5\i0 \uc0\u8722 -\f6\i \uc0\u956 -\f5\i0 |/ -\f6\i b -\f5\i0 )/2 -\f6\i b -\f4\i0 , where -\f6\i \uc0\u956 -\f4\i0 is the mean and -\f6\i b -\f4\i0 is the scale parameter. A Laplace distribution is sometimes used to model a mix of both deleterious and beneficial mutations.\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 - -\f3\fs18 \cf0 "w" -\f4\fs22 \'96 A -\f0\b W -\f4\b0 eibull-distributed fitness effect. This DFE type is specified by a scale parameter and a shape parameter. The Weibull distribution from which mutations are drawn is given by the probability density function -\f6\i P -\f5\i0 ( -\f6\i s -\f5\i0 \'a0|\'a0 -\f8\i \uc0\u955 -\f5\i0 , -\f6\i k -\f5\i0 )\'a0= ( -\f6\i k -\f5\i0 / -\f8\i \uc0\u955 -\f6 \super k -\f5\i0 \nosupersub ) -\f6\i s\super k -\f5\i0 \uc0\u8722 1\nosupersub exp(\uc0\u8722 ( -\f6\i s -\f5\i0 / -\f8\i \uc0\u955 -\f5\i0 ) -\f6\i \super k -\f5\i0 \nosupersub ) -\f4 , where -\f8\i \uc0\u955 -\f4\i0 is the scale parameter and -\f6\i k -\f4\i0 is the shape parameter. This parameterization is the same as for the Eidos function -\f3\fs18 rweibull() -\f4\fs22 . A Weibull distribution is often used to model mutations following extreme-value theory.\ - -\f3\fs18 "s" -\f4\fs22 \'96 A -\f0\b s -\f4\b0 cript-based fitness effect. This DFE type is specified by a script parameter of type -\f3\fs18 string -\f4\fs22 , specifying an Eidos script to be executed to produce each new selection coefficient. For example, the script -\f3\fs18 "return rbinom(1);" -\f4\fs22 could be used to generate selection coefficients drawn from a binomial distribution, using the Eidos function -\f3\fs18 rbinom() -\f4\fs22 , even though that mutational distribution is not supported by SLiM directly. The script must return a singleton float or integer.\ -Note that these distributions can in principle produce selection coefficients smaller than -\f3\fs18 -1.0. -\f4\fs22 In that case -\f5 , -\f4 the mutations will be evaluated as \'93lethal\'94 by SLiM, and the relative fitness of the individual will be set to -\f3\fs18 0.0 -\f5\fs22 . -\fs20 \ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - -\f3\fs18 \cf2 dominanceCoeff <\'96> (float$)\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf2 The default dominance coefficient used for mutations of this type when heterozygous. This default value is taken by new mutations of this mutation type when they are created, as the value of their -\f3\fs18 dominanceCoeff -\f4\fs20 property, but that can be changed later with the -\f3\fs18 Mutation -\f4\fs20 method -\f3\fs18 setDominanceCoeff() -\f4\fs20 .\ -Note that the dominance coefficient is not bounded. A dominance coefficient greater than -\f3\fs18 1.0 -\f4\fs20 may be used to achieve an overdominance effect. By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ -Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation type -\f3\fs18 muttype -\f4\fs20 \'92s dominance coefficient to some number -\f3\fs18 x -\f4\fs20 , -\f3\fs18 muttype.dominanceCoeff==x -\f4\fs20 may be -\f3\fs18 F -\f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly. Instead, it is recommended to use the -\f3\fs18 id -\f4\fs20 or -\f3\fs18 tag -\f4\fs20 properties to identify particular mutation types.\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - -\f3\fs18 \cf2 hemizygousDominanceCoeff <\'96> (float$)\ +\f3\fs18 \cf2 \kerning1\expnd0\expndtw0 hemizygousDominanceCoeff <\'96> (float$)\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 The dominance coefficient used for mutations of this type when they occur opposite a null haplosome (as can occur in sex-chromosome models and models involving a mix of haploids and diploids). This defaults to @@ -6603,24 +6371,125 @@ The species to which the target object belongs.\ \f1\i\fs22 \cf0 5.11.2 \f2\fs18 MutationType \f1\fs22 methods\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 \expnd0\expndtw0\kerning0 -\'96\'a0(float)drawSelectionCoefficient([integer$\'a0n\'a0=\'a01])\ +\f3\i0\fs18 \cf2 \'96\'a0(float$)defaultDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their +\f3\fs18 dominanceCoeff +\f4\fs20 property, but that can be changed later with the +\f3\fs18 Mutation +\f4\fs20 method +\f3\fs18 setDominanceCoeff() +\f4\fs20 .\ +Note that dominance coefficients are not bounded. A dominance coefficient greater than +\f3\fs18 1.0 +\f4\fs20 may be used to achieve an overdominance effect. By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ +Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision +\f3\fs18 float +\f4\fs20 , not the double-precision +\f3\fs18 float +\f4\fs20 type normally used by Eidos. This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.\ +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(fs)distributionParamsForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the parameters that configure the distribution of effects for the specified trait or traits. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The distribution parameters will be of type +\f3\fs18 string +\f4\fs20 for DFE type +\f3\fs18 "s" +\f4\fs20 , and type +\f3\fs18 float +\f4\fs20 for all other DFE types.\ +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(string$)distributionTypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the type of distribution of effects for the specified trait or traits. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The distribution type will be one of +\f3\fs18 "f" +\f4\fs20 , +\f3\fs18 "g" +\f4\fs20 , +\f3\fs18 "e" +\f4\fs20 , +\f3\fs18 "n" +\f4\fs20 , +\f3\fs18 "p" +\f4\fs20 , +\f3\fs18 "w" +\f4\fs20 , or +\f3\fs18 "s" +\f4\fs20 , as discussed in the +\f3\fs18 MutationType +\f4\fs20 class documentation.\ +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(float)drawEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Draws and returns a vector of \f3\fs18 n -\f4\fs20 selection coefficients using the currently defined distribution of fitness effects (DFE) for the target mutation type. If the DFE is type +\f4\fs20 mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. See the +\f3\fs18 MutationType +\f4\fs20 class documentation for discussion of the supported distributions and their uses. If the distribution of effects is of type \f3\fs18 "s" -\f4\fs20 , this method will result in synchronous execution of the DFE\'92s script.\ +\f4\fs20 , this method will result in synchronous execution of the script associated with the distribution of effects.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 \kerning1\expnd0\expndtw0 \'96\'a0(void)setDistribution(string$\'a0distributionType, ...) -\f5 \ +\f3\fs18 \cf2 \'96\'a0(void)setDefaultDominanceForTrait(Nio\'a0trait, float\'a0dominance)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 Set the distribution of fitness effects for a mutation type. The +\f4\fs20 \cf2 Set the default dominance coefficient for a specified trait or traits, for the target mutation type. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The value of +\f3\fs18 defaultDominance +\f4\fs20 must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of +\f3\fs18 defaultDominance +\f4\fs20 is used for each corresponding trait).\ +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(void)setDistributionForTrait(Nio\'a0trait, string$\'a0distributionType, ...)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Set the distribution of effects for a specified trait or traits, for the target mutation type. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species.\ +The \f3\fs18 distributionType \f4\fs20 may be \f3\fs18 "f" @@ -6662,7 +6531,9 @@ The species to which the target object belongs.\ \f3\fs18 "s" \f4\fs20 , in which case the ellipsis should supply a \f3\fs18 string$ -\f4\fs20 Eidos script parameter. The DFE for a mutation type is normally a constant in simulations, so be sure you know what you are doing.\ +\f4\fs20 Eidos script parameter. See the +\f3\fs18 MutationType +\f4\fs20 class documentation for discussion of these distributions and their uses. The distribution of effects for a mutation type is normally a constant in simulations, so be sure you know what you are doing.\ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 \f0\b\fs22 \cf2 5.12 Class Plot\ @@ -9378,8 +9249,7 @@ Beginning with SLiM 3.3, the \f4\i0 include the nucleotides associated with any nucleotide-based mutations; the \f3\fs18 ancestralNucleotides \f4\fs20 flag governs only the ancestral sequence.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \kerning1\expnd0\expndtw0 Beginning with SLiM 3.5, the +\kerning1\expnd0\expndtw0 Beginning with SLiM 3.5, the \f3\fs18 pedigreeIDs \f4\fs20 parameter may be used to request that pedigree IDs be written out (and read in by \f3\fs18 readFromPopulationFile() @@ -9518,8 +9388,7 @@ Beginning with SLiM 5.0, the \f4\fs20 , etc.) will be defined to refer to the new \f3\fs18 Subpopulation \f4\fs20 objects loaded from the file. Note that fitness values are not calculated as a side effect of this call (because the simulation will often need to evaluate interactions or modify other state prior to doing so).\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \expnd0\expndtw0\kerning0 +\expnd0\expndtw0\kerning0 In SLiM 2.3 and later when using the WF model, calling \f3\fs18 readFromPopulationFile() \f4\fs20 from any context other than a @@ -9538,8 +9407,7 @@ In SLiM 3.0 when using the nonWF model, calling \f4\fs20 event is almost always correct in nonWF models, so that fitness values can be automatically recalculated by SLiM at the usual time in the tick cycle without the need to force their recalculation (see comments on \f3\fs18 recalculateFitness() \f4\fs20 ).\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \kerning1\expnd0\expndtw0 This method changes the tick and cycle counters to the tick and cycle read from the file. If you do not want these counters to be changed, you can change them back after reading, by setting +\kerning1\expnd0\expndtw0 This method changes the tick and cycle counters to the tick and cycle read from the file. If you do not want these counters to be changed, you can change them back after reading, by setting \f3\fs18 community.tick \f4\fs20 and \f3\fs18 sim.cycle @@ -9560,14 +9428,12 @@ Any changes made to the structure of the species (mutation types, genomic elemen \f3\fs18 tag \f4\fs20 values of individuals; if a given option is enabled and the corresponding information is saved, then that information will be restored, otherwise it will not be.\ As of SLiM 2.3, this method will read and restore the spatial positions of individuals if that information is present in the output file and the species has enabled continuous space. If spatial positions are present in the output file but the species has not enabled continuous space (or the number of spatial dimensions does not match), an error will result. If the species has enabled continuous space but spatial positions are not present in the output file, the spatial positions of the individuals read will be undefined, but an error is not raised.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \expnd0\expndtw0\kerning0 +\expnd0\expndtw0\kerning0 As of SLiM 3.0, this method will read and restore the ages of individuals if that information is present in the output file and the simulation is based upon the nonWF model. If ages are present but the simulation uses a WF model, an error will result; the WF model does not use age information. If ages are not present but the simulation uses a nonWF model, an error will also result; the nonWF model requires age information.\ As of SLiM 3.3, this method will restore the nucleotides of nucleotide-based mutations, and will restore the ancestral nucleotide sequence, if that information is present in the output file. Loading an output file that contains nucleotide information in a non-nucleotide-based model, and \f1\i vice versa \f4\i0 , will produce an error.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \kerning1\expnd0\expndtw0 As of SLiM 3.5, this method will read and restore the pedigree IDs of individuals and haplosomes if that information is present in the output file (as requested with +\kerning1\expnd0\expndtw0 As of SLiM 3.5, this method will read and restore the pedigree IDs of individuals and haplosomes if that information is present in the output file (as requested with \f3\fs18 outputFull(pedigreeIDs=T) \f4\fs20 ) \f1\i and diff --git a/SLiMgui/SLiMWindowController.mm b/SLiMgui/SLiMWindowController.mm index 03f85915..5a8567ca 100644 --- a/SLiMgui/SLiMWindowController.mm +++ b/SLiMgui/SLiMWindowController.mm @@ -4407,6 +4407,7 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu std::advance(mutTypeIter, rowIndex); slim_objectid_t mutTypeID = mutTypeIter->first; MutationType *mutationType = mutTypeIter->second; + EffectDistributionInfo &ed_info = mutationType->effect_distributions_[0]; // FIXME MULTITRAIT if (aTableColumn == mutTypeIDColumn) { @@ -4419,11 +4420,11 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu } else if (aTableColumn == mutTypeDominanceColumn) { - return [NSString stringWithFormat:@"%.3f", mutationType->default_dominance_coeff_]; + return [NSString stringWithFormat:@"%.3f", ed_info.default_dominance_coeff_]; } else if (aTableColumn == mutTypeDFETypeColumn) { - switch (mutationType->dfe_type_) + switch (ed_info.dfe_type_) { case DFEType::kFixed: return @"fixed"; case DFEType::kGamma: return @"gamma"; @@ -4438,28 +4439,28 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu { NSMutableString *paramString = [[NSMutableString alloc] init]; - if (mutationType->dfe_type_ == DFEType::kScript) + if (ed_info.dfe_type_ == DFEType::kScript) { // DFE type 's' has parameters of type string - for (unsigned int paramIndex = 0; paramIndex < mutationType->dfe_strings_.size(); ++paramIndex) + for (unsigned int paramIndex = 0; paramIndex < ed_info.dfe_strings_.size(); ++paramIndex) { - const char *dfe_string = mutationType->dfe_strings_[paramIndex].c_str(); + const char *dfe_string = ed_info.dfe_strings_[paramIndex].c_str(); NSString *ns_dfe_string = [NSString stringWithUTF8String:dfe_string]; [paramString appendFormat:@"\"%@\"", ns_dfe_string]; - if (paramIndex < mutationType->dfe_strings_.size() - 1) + if (paramIndex < ed_info.dfe_strings_.size() - 1) [paramString appendString:@", "]; } } else { // All other DFEs have parameters of type double - for (unsigned int paramIndex = 0; paramIndex < mutationType->dfe_parameters_.size(); ++paramIndex) + for (unsigned int paramIndex = 0; paramIndex < ed_info.dfe_parameters_.size(); ++paramIndex) { NSString *paramSymbol = @""; - switch (mutationType->dfe_type_) + switch (ed_info.dfe_type_) { case DFEType::kFixed: paramSymbol = @"s"; break; case DFEType::kGamma: paramSymbol = (paramIndex == 0 ? @"s̄" : @"α"); break; @@ -4470,9 +4471,9 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu case DFEType::kScript: break; } - [paramString appendFormat:@"%@=%.3f", paramSymbol, mutationType->dfe_parameters_[paramIndex]]; + [paramString appendFormat:@"%@=%.3f", paramSymbol, ed_info.dfe_parameters_[paramIndex]]; - if (paramIndex < mutationType->dfe_parameters_.size() - 1) + if (paramIndex < ed_info.dfe_parameters_.size() - 1) [paramString appendString:@", "]; } } diff --git a/VERSIONS b/VERSIONS index 25cc6057..df4099b9 100644 --- a/VERSIONS +++ b/VERSIONS @@ -18,23 +18,32 @@ development head (in the master branch): multitrait branch: add new Eidos SLiM class, Trait - add (object$)initializeTrait(s$ name, s$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [l$ directFitnessEffect = F]) - add Species property traits => (object) to simply get all traits defined for a species - add Species methods – (object)traitsWithIndices(integer indices) and – (object)traitsWithNames(string names) - add Trait properties: - baselineOffset <-> (float$) - directFitnessEffect <-> (logical$) - index => (integer$) - individualOffsetMean <-> (float$) - individualOffsetSD <-> (float$) - name => (string$) - species => (object$) - tag <-> (integer$) - type => (string$) - add Community property allTraits => (object) - add a dominanceCoeff property to Mutation, with a value inherited from MutationType's property (which is now just the default value) - add dominanceCoeff properties to Mutation and Substitution - add a setDominanceCoeff() method to Mutation, yay! + add (object$)initializeTrait(s$ name, s$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [l$ directFitnessEffect = F]) + add Species property traits => (object) to simply get all traits defined for a species + add Species methods – (object)traitsWithIndices(integer indices) and – (object)traitsWithNames(string names) + add Trait properties: + baselineOffset <-> (float$) + directFitnessEffect <-> (logical$) + index => (integer$) + individualOffsetMean <-> (float$) + individualOffsetSD <-> (float$) + name => (string$) + species => (object$) + tag <-> (integer$) + type => (string$) + add Community property allTraits => (object) + add a C++ dominance_coeff_ property to Mutation, with a value inherited from MutationType's property (which is now just the default value) + add dominanceCoeff properties to Mutation and Substitution + add a setDominanceCoeff() method to Mutation, yay! + fix calcInbreedingLoad() to use muts.dominanceCoeff instead of muts.mutationType.dominanceCoeff + revamp MutationType for multiple traits + remove MutationType properties dominanceCoeff, distributionType, and distributionParams properties + add MutationType methods defaultDominanceForTrait(), distributionTypeForTrait(), and distributionParamsForTrait() + change MutationType method setDistribution() to setDistributionForTrait() + change MutationType method drawSelectionCoefficient() to drawEffectForTrait() + add SLiMgui autofixing for all of the above changes + add MutationType method setDefaultDominanceForTrait() (approximately replacing writing into the dominanceCoeff property, but this should not autofix) + transition MutationType's internals to keep a separate DE for each trait version 5.0 (Eidos version 4.0): diff --git a/core/chromosome.cpp b/core/chromosome.cpp index a88fb825..a67d0a4e 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1035,7 +1035,7 @@ MutationIndex Chromosome::DrawNewMutation(std::pair(mutation_type_ptr->DrawSelectionCoefficient()); + slim_selcoeff_t selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE, SINCE WE DO NOT KNOW WHAT HAPLOSOME WE WILL BE INSERTED INTO! THIS IS THE CALLER'S RESPONSIBILITY! MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); @@ -1043,7 +1043,7 @@ MutationIndex Chromosome::DrawNewMutation(std::pairdefault_dominance_coeff_, p_subpop_index, p_tick, -1); + new (mutation) Mutation(mutation_type_ptr, index_, p_position.first, selection_coeff, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, p_subpop_index, p_tick, -1); // FIXME MULTITRAIT // addition to the main registry and the muttype registries will happen if the new mutation clears the stacking policy @@ -1401,13 +1401,13 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pair(mutation_type_ptr->DrawSelectionCoefficient()); + slim_selcoeff_t selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE! THIS IS THE CALLER'S RESPONSIBILITY! MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); Mutation *mutation = gSLiM_Mutation_Block + new_mut_index; - new (mutation) Mutation(mutation_type_ptr, index_, position, selection_coeff, mutation_type_ptr->default_dominance_coeff_, p_subpop_index, p_tick, nucleotide); + new (mutation) Mutation(mutation_type_ptr, index_, position, selection_coeff, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, p_subpop_index, p_tick, nucleotide); // FIXME MULTITRAIT // Call mutation() callbacks if there are any if (p_mutation_callbacks) diff --git a/core/genomic_element_type.cpp b/core/genomic_element_type.cpp index 921dbf7b..c86d016f 100644 --- a/core/genomic_element_type.cpp +++ b/core/genomic_element_type.cpp @@ -405,7 +405,8 @@ EidosValue_SP GenomicElementType::ExecuteMethod_setMutationFractions(EidosGlobal mutation_fractions.emplace_back(proportion); // check whether we are now using a mutation type that is non-neutral; check and set pure_neutral_ - if ((mutation_type_ptr->dfe_type_ != DFEType::kFixed) || (mutation_type_ptr->dfe_parameters_[0] != 0.0)) + if (!mutation_type_ptr->IsPureNeutralDFE()) + //if ((mutation_type_ptr->dfe_type_ != DFEType::kFixed) || (mutation_type_ptr->dfe_parameters_[0] != 0.0)) species_.pure_neutral_ = false; } diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 07e1439c..ab28ecfe 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2875,7 +2875,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID if (arg_selcoeff) selection_coeff = arg_selcoeff->NumericAtIndex_NOCAST(mut_parameter_index, nullptr); else - selection_coeff = mutation_type_ptr->DrawSelectionCoefficient(); + selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT } if (origin_subpop_count != 1) @@ -2897,7 +2897,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->default_dominance_coeff_, origin_subpop_id, origin_tick, (int8_t)nucleotide); + Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, origin_subpop_id, origin_tick, (int8_t)nucleotide); // FIXME MULTITRAIT // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DFE), so we set all_pure_neutral_DFE_ also @@ -3380,7 +3380,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr for (int mut_index = 0; mut_index < segsites; ++mut_index) { slim_position_t position = positions[mut_index]; - double selection_coeff = mutation_type_ptr->DrawSelectionCoefficient(); + double selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT slim_objectid_t subpop_index = -1; slim_tick_t origin_tick = community.Tick(); int8_t nucleotide = -1; @@ -3398,7 +3398,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->default_dominance_coeff_, subpop_index, origin_tick, nucleotide); + Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, subpop_index, origin_tick, nucleotide); // FIXME MULTITRAIT // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ if (selection_coeff != 0.0) @@ -3955,7 +3955,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt if (info_domcoeffs.size() > 0) dominance_coeff = info_domcoeffs[alt_allele_index]; else - dominance_coeff = mutation_type_ptr->default_dominance_coeff_; + dominance_coeff = mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_; // FIXME MULTITRAIT // get the selection coefficient from S, or draw one from the mutation type slim_selcoeff_t selection_coeff; @@ -3963,7 +3963,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt if (info_selcoeffs.size() > 0) selection_coeff = info_selcoeffs[alt_allele_index]; else - selection_coeff = static_cast(mutation_type_ptr->DrawSelectionCoefficient()); + selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // get the subpop index from PO, or set to -1; no bounds checking on this slim_objectid_t subpop_index = -1; diff --git a/core/individual.cpp b/core/individual.cpp index 5be14b3c..e13f8485 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -4570,7 +4570,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (info_domcoeffs.size() > 0) dominance_coeff = info_domcoeffs[alt_allele_index]; else - dominance_coeff = mutation_type_ptr->default_dominance_coeff_; + dominance_coeff = mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_; // FIXME MULTITRAIT // get the selection coefficient from S, or draw one from the mutation type slim_selcoeff_t selection_coeff; @@ -4578,7 +4578,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (info_selcoeffs.size() > 0) selection_coeff = info_selcoeffs[alt_allele_index]; else - selection_coeff = static_cast(mutation_type_ptr->DrawSelectionCoefficient()); + selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // get the subpop index from PO, or set to -1; no bounds checking on this slim_objectid_t subpop_index = -1; diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 457fe9f8..22695b8b 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -61,7 +61,7 @@ MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_i MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DFEType p_dfe_type, std::vector p_dfe_parameters, std::vector p_dfe_strings) : #endif self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStringWithPrefix('m', p_mutation_type_id)), EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(this, gSLiM_MutationType_Class))), - species_(p_species), mutation_type_id_(p_mutation_type_id), default_dominance_coeff_(static_cast(p_dominance_coeff)), hemizygous_dominance_coeff_(1.0), dfe_type_(p_dfe_type), dfe_parameters_(std::move(p_dfe_parameters)), dfe_strings_(std::move(p_dfe_strings)), nucleotide_based_(p_nuc_based), convert_to_substitution_(false), stack_policy_(MutationStackPolicy::kStack), stack_group_(p_mutation_type_id), cached_dfe_script_(nullptr) + species_(p_species), mutation_type_id_(p_mutation_type_id), hemizygous_dominance_coeff_(1.0), nucleotide_based_(p_nuc_based), convert_to_substitution_(false), stack_policy_(MutationStackPolicy::kStack), stack_group_(p_mutation_type_id), cached_dfe_script_(nullptr) #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES , muttype_registry_call_count_(0), keeping_muttype_registry_(false) #endif @@ -76,7 +76,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr if (species_.community_.ModelType() == SLiMModelType::kModelTypeWF) convert_to_substitution_ = true; - if ((dfe_parameters_.size() == 0) && (dfe_strings_.size() == 0)) + if ((p_dfe_parameters.size() == 0) && (p_dfe_strings.size() == 0)) EIDOS_TERMINATION << "ERROR (MutationType::MutationType): invalid mutation type parameters." << EidosTerminate(); // intentionally no bounds checks for DFE parameters; the count of DFE parameters is checked prior to construction // intentionally no bounds check for dominance_coeff_ @@ -84,7 +84,33 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr // determine whether this mutation type is initially pure neutral; note that this flag will be // cleared if any mutation of this type has its selection coefficient changed // note also that we do not set Species.pure_neutral_ here; we wait until this muttype is used - all_pure_neutral_DFE_ = ((dfe_type_ == DFEType::kFixed) && (dfe_parameters_[0] == 0.0)); + all_pure_neutral_DFE_ = ((p_dfe_type == DFEType::kFixed) && (p_dfe_parameters[0] == 0.0)); + + // set up DE entries for all traits + int64_t trait_count = species_.TraitCount(); + + for (int64_t trait_index = 0; trait_index < trait_count; trait_index++) + { + EffectDistributionInfo ed_info; + + if (trait_index == 0) + { + // the DE for the first trait gets set from the parameters passed in + ed_info.default_dominance_coeff_ = static_cast(p_dominance_coeff); + ed_info.dfe_type_ = p_dfe_type; + ed_info.dfe_parameters_ = std::move(p_dfe_parameters); + ed_info.dfe_strings_ = std::move(p_dfe_strings); + } + else + { + // remaining traits default to neutral, with a dominance coefficient of 0.0 + ed_info.default_dominance_coeff_ = 0.5; + ed_info.dfe_type_ = DFEType::kFixed; + ed_info.dfe_parameters_.push_back(0.0); + } + + effect_distributions_.emplace_back(ed_info); + } // Nucleotide-based mutations use a special stacking group, -1, and always use stacking policy "l" if (p_nuc_based) @@ -221,44 +247,46 @@ void MutationType::ParseDFEParameters(std::string &p_dfe_type_string, const Eido } } -double MutationType::DrawSelectionCoefficient(void) const +double MutationType::DrawEffectForTrait(int64_t p_trait_index) const { + const EffectDistributionInfo &de_info = effect_distributions_[p_trait_index]; + // BCH 11/11/2022: Note that EIDOS_GSL_RNG(omp_get_thread_num()) can take a little bit of time when running // parallel. We don't want to pass the RNG in, though, because that would slow down the single-threaded // case, where the EIDOS_GSL_RNG(omp_get_thread_num()) call basically compiles away to a global var access. // So here and in similar places, we fetch the RNG rather than passing it in to keep single-threaded fast. - switch (dfe_type_) + switch (de_info.dfe_type_) { - case DFEType::kFixed: return dfe_parameters_[0]; + case DFEType::kFixed: return de_info.dfe_parameters_[0]; case DFEType::kGamma: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_gamma(rng, dfe_parameters_[1], dfe_parameters_[0] / dfe_parameters_[1]); + return gsl_ran_gamma(rng, de_info.dfe_parameters_[1], de_info.dfe_parameters_[0] / de_info.dfe_parameters_[1]); } case DFEType::kExponential: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_exponential(rng, dfe_parameters_[0]); + return gsl_ran_exponential(rng, de_info.dfe_parameters_[0]); } case DFEType::kNormal: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_gaussian(rng, dfe_parameters_[1]) + dfe_parameters_[0]; + return gsl_ran_gaussian(rng, de_info.dfe_parameters_[1]) + de_info.dfe_parameters_[0]; } case DFEType::kWeibull: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_weibull(rng, dfe_parameters_[0], dfe_parameters_[1]); + return gsl_ran_weibull(rng, de_info.dfe_parameters_[0], de_info.dfe_parameters_[1]); } case DFEType::kLaplace: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_laplace(rng, dfe_parameters_[1]) + dfe_parameters_[0]; + return gsl_ran_laplace(rng, de_info.dfe_parameters_[1]) + de_info.dfe_parameters_[0]; } case DFEType::kScript: @@ -269,9 +297,9 @@ double MutationType::DrawSelectionCoefficient(void) const #ifdef DEBUG_LOCKS_ENABLED // When running multi-threaded, this code is not re-entrant because it runs an Eidos interpreter. We use // EidosDebugLock to enforce that. In addition, it can raise, so the caller must be prepared for that. - static EidosDebugLock DrawSelectionCoefficient_InterpreterLock("DrawSelectionCoefficient_InterpreterLock"); + static EidosDebugLock DrawEffectForTrait_InterpreterLock("DrawEffectForTrait_InterpreterLock"); - DrawSelectionCoefficient_InterpreterLock.start_critical(0); + DrawEffectForTrait_InterpreterLock.start_critical(0); #endif double sel_coeff; @@ -286,7 +314,7 @@ double MutationType::DrawSelectionCoefficient(void) const // We try to do tokenization and parsing once per script, by caching the script if (!cached_dfe_script_) { - std::string script_string = dfe_strings_[0]; + std::string script_string = de_info.dfe_strings_[0]; cached_dfe_script_ = new EidosScript(script_string); gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, cached_dfe_script_}; @@ -301,17 +329,17 @@ double MutationType::DrawSelectionCoefficient(void) const if (gEidosTerminateThrows) { gEidosErrorContext = error_context_save; - TranslateErrorContextToUserScript("DrawSelectionCoefficient()"); + TranslateErrorContextToUserScript("DrawEffectForTrait()"); } delete cached_dfe_script_; cached_dfe_script_ = nullptr; #ifdef DEBUG_LOCKS_ENABLED - DrawSelectionCoefficient_InterpreterLock.end_critical(); + DrawEffectForTrait_InterpreterLock.end_critical(); #endif - EIDOS_TERMINATION << "ERROR (MutationType::DrawSelectionCoefficient): tokenize/parse error in type 's' DFE callback script." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectForTrait): tokenize/parse error in type 's' DFE callback script." << EidosTerminate(nullptr); } } @@ -335,7 +363,7 @@ double MutationType::DrawSelectionCoefficient(void) const else if ((result_type == EidosValueType::kValueInt) && (result_count == 1)) sel_coeff = result->IntData()[0]; else - EIDOS_TERMINATION << "ERROR (MutationType::DrawSelectionCoefficient): type 's' DFE callbacks must provide a singleton float or integer return value." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectForTrait): type 's' DFE callbacks must provide a singleton float or integer return value." << EidosTerminate(nullptr); } catch (...) { @@ -350,12 +378,12 @@ double MutationType::DrawSelectionCoefficient(void) const if (!gEidosErrorContext.currentScript || (gEidosErrorContext.currentScript->UserScriptUTF16Offset() == -1)) { gEidosErrorContext = error_context_save; - TranslateErrorContextToUserScript("DrawSelectionCoefficient()"); + TranslateErrorContextToUserScript("DrawEffectForTrait()"); } } #ifdef DEBUG_LOCKS_ENABLED - DrawSelectionCoefficient_InterpreterLock.end_critical(); + DrawEffectForTrait_InterpreterLock.end_critical(); #endif throw; @@ -365,17 +393,18 @@ double MutationType::DrawSelectionCoefficient(void) const gEidosErrorContext = error_context_save; #ifdef DEBUG_LOCKS_ENABLED - DrawSelectionCoefficient_InterpreterLock.end_critical(); + DrawEffectForTrait_InterpreterLock.end_critical(); #endif return sel_coeff; } } - EIDOS_TERMINATION << "ERROR (MutationType::DrawSelectionCoefficient): (internal error) unexpected dfe_type_ value." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectForTrait): (internal error) unexpected dfe_type_ value." << EidosTerminate(); } // This is unused except by debugging code and in the debugger itself -std::ostream &operator<<(std::ostream &p_outstream, const MutationType &p_mutation_type) +// FIXME MULTITRAIT commented this out for now +/*std::ostream &operator<<(std::ostream &p_outstream, const MutationType &p_mutation_type) { p_outstream << "MutationType{default_dominance_coeff_ " << p_mutation_type.default_dominance_coeff_ << ", dfe_type_ '" << p_mutation_type.dfe_type_ << "', dfe_parameters_ <"; @@ -403,7 +432,7 @@ std::ostream &operator<<(std::ostream &p_outstream, const MutationType &p_mutati p_outstream << ">}"; return p_outstream; -} +}*/ // @@ -435,49 +464,6 @@ EidosValue_SP MutationType::GetProperty(EidosGlobalStringID p_property_id) cached_value_muttype_id_ = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(mutation_type_id_)); return cached_value_muttype_id_; } - case gID_distributionType: - { - static EidosValue_SP static_dfe_string_f; - static EidosValue_SP static_dfe_string_g; - static EidosValue_SP static_dfe_string_e; - static EidosValue_SP static_dfe_string_n; - static EidosValue_SP static_dfe_string_w; - static EidosValue_SP static_dfe_string_p; - static EidosValue_SP static_dfe_string_s; - -#pragma omp critical (GetProperty_distributionType_cache) - { - if (!static_dfe_string_f) - { - static_dfe_string_f = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_f)); - static_dfe_string_g = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_g)); - static_dfe_string_e = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_e)); - static_dfe_string_n = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gEidosStr_n)); - static_dfe_string_w = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_w)); - static_dfe_string_p = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_p)); - static_dfe_string_s = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gEidosStr_s)); - } - } - - switch (dfe_type_) - { - case DFEType::kFixed: return static_dfe_string_f; - case DFEType::kGamma: return static_dfe_string_g; - case DFEType::kExponential: return static_dfe_string_e; - case DFEType::kNormal: return static_dfe_string_n; - case DFEType::kWeibull: return static_dfe_string_w; - case DFEType::kLaplace: return static_dfe_string_p; - case DFEType::kScript: return static_dfe_string_s; - default: return gStaticEidosValueNULL; // never hit; here to make the compiler happy - } - } - case gID_distributionParams: - { - if (dfe_parameters_.size() > 0) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dfe_parameters_)); - else - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(dfe_strings_)); - } case gID_species: { return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(&species_, gSLiM_Species_Class)); @@ -490,8 +476,6 @@ EidosValue_SP MutationType::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(color_sub_)); case gID_convertToSubstitution: return (convert_to_substitution_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); - case gID_dominanceCoeff: // ACCELERATED - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(default_dominance_coeff_)); case gID_hemizygousDominanceCoeff: return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(hemizygous_dominance_coeff_)); case gID_mutationStackGroup: @@ -572,20 +556,6 @@ EidosValue *MutationType::GetProperty_Accelerated_tag(EidosObject **p_values, si return int_result; } -EidosValue *MutationType::GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size) -{ - EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); - - for (size_t value_index = 0; value_index < p_values_size; ++value_index) - { - MutationType *value = (MutationType *)(p_values[value_index]); - - float_result->set_float_no_check(value->default_dominance_coeff_, value_index); - } - - return float_result; -} - void MutationType::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) { // All of our strings are in the global registry, so we can require a successful lookup @@ -615,22 +585,6 @@ void MutationType::SetProperty(EidosGlobalStringID p_property_id, const EidosVal return; } - case gID_dominanceCoeff: - { - double value = p_value.FloatAtIndex_NOCAST(0, nullptr); - - default_dominance_coeff_ = static_cast(value); // intentionally no bounds check - - // BCH 7/2/2025: Changing the default dominance coefficient no longer means that the cached fitness - // effects of all mutations using this type become invalid; it is now just the *default* coefficient, - // and changing it does not change the state of mutations that have already derived from it. We do - // still want to let the community know that a mutation type has changed, though. - //species_.any_dominance_coeff_changed_ = true; - species_.community_.mutation_types_changed_ = true; - - return; - } - case gID_hemizygousDominanceCoeff: { double value = p_value.FloatAtIndex_NOCAST(0, nullptr); @@ -734,62 +688,267 @@ EidosValue_SP MutationType::ExecuteInstanceMethod(EidosGlobalStringID p_method_i { switch (p_method_id) { - case gID_drawSelectionCoefficient: return ExecuteMethod_drawSelectionCoefficient(p_method_id, p_arguments, p_interpreter); - case gID_setDistribution: return ExecuteMethod_setDistribution(p_method_id, p_arguments, p_interpreter); - default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); + case gID_defaultDominanceForTrait: return ExecuteMethod_defaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_distributionTypeForTrait: return ExecuteMethod_distributionTypeForTrait(p_method_id, p_arguments, p_interpreter); + case gID_distributionParamsForTrait: return ExecuteMethod_distributionParamsForTrait(p_method_id, p_arguments, p_interpreter); + case gID_drawEffectForTrait: return ExecuteMethod_drawEffectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_setDefaultDominanceForTrait: return ExecuteMethod_setDefaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_setDistributionForTrait: return ExecuteMethod_setDistributionForTrait(p_method_id, p_arguments, p_interpreter); + default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } } -// ********************* - (float)drawSelectionCoefficient([integer$ n = 1]) +// ********************* - (float$)defaultDominanceForTrait([Nio trait = NULL]) // -EidosValue_SP MutationType::ExecuteMethod_drawSelectionCoefficient(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultDominanceForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + EffectDistributionInfo &de_info = effect_distributions_[trait_index]; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(de_info.default_dominance_coeff_)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &de_info = effect_distributions_[trait_index]; + + float_result->push_float_no_check(de_info.default_dominance_coeff_); + } + + return EidosValue_SP(float_result); + } +} + +// ********************* - (fs)distributionParamsForTrait([Nio trait = NULL]) +// +EidosValue_SP MutationType::ExecuteMethod_distributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultDominanceForTrait"); + + // decide whether doing floats or strings; must be the same for all + bool is_float = false; + bool is_string = false; + + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &de_info = effect_distributions_[trait_index]; + + if (de_info.dfe_parameters_.size() > 0) + is_float = true; + else + is_string = true; + } + + if (is_float && is_string) + EIDOS_TERMINATION << "ERROR (ExecuteMethod_drawEffectForTrait): drawEffectForTrait() requires all specified traits to have either float or string parameters (not a mixture) for their distributions of effects." << EidosTerminate(nullptr); + + if (is_float) + { + EidosValue_Float *float_result = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(); + + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &de_info = effect_distributions_[trait_index]; + + for (double param : de_info.dfe_parameters_) + float_result->push_float(param); + } + + return EidosValue_SP(float_result); + } + else + { + EidosValue_String *string_result = new (gEidosValuePool->AllocateChunk()) EidosValue_String(); + + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &de_info = effect_distributions_[trait_index]; + + for (const std::string ¶m : de_info.dfe_strings_) + string_result->PushString(param); + } + + return EidosValue_SP(string_result); + } +} + +// ********************* - (string$)distributionTypeForTrait([Nio trait = NULL]) +// +EidosValue_SP MutationType::ExecuteMethod_distributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultDominanceForTrait"); + + // assemble the result + EidosValue_String *string_result = new (gEidosValuePool->AllocateChunk()) EidosValue_String(); + + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &de_info = effect_distributions_[trait_index]; + + switch (de_info.dfe_type_) + { + case DFEType::kFixed: string_result->PushString(gStr_f); break; + case DFEType::kGamma: string_result->PushString(gStr_g); break; + case DFEType::kExponential: string_result->PushString(gStr_e); break; + case DFEType::kNormal: string_result->PushString(gEidosStr_n); break; + case DFEType::kWeibull: string_result->PushString(gStr_w); break; + case DFEType::kLaplace: string_result->PushString(gStr_p); break; + case DFEType::kScript: string_result->PushString(gEidosStr_s); break; + default: return gStaticEidosValueNULL; // never hit; here to make the compiler happy + } + } + + return EidosValue_SP(string_result); +} + +// ********************* - (float)drawEffectForTrait([Nio trait = NULL], [integer$ n = 1]) +// +EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue_SP result_SP(nullptr); - EidosValue *n_value = p_arguments[0].get(); + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *n_value = p_arguments[1].get(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultDominanceForTrait"); + + // get the number of effects to draw int64_t num_draws = n_value->IntAtIndex_NOCAST(0, nullptr); if (num_draws < 0) - EIDOS_TERMINATION << "ERROR (ExecuteMethod_drawSelectionCoefficient): drawSelectionCoefficient() requires n to be greater than or equal to 0 (" << num_draws << " supplied)." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (ExecuteMethod_drawEffectForTrait): drawEffectForTrait() requires n to be greater than or equal to 0 (" << num_draws << " supplied)." << EidosTerminate(nullptr); - EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(num_draws); - result_SP = EidosValue_SP(float_result); + if ((trait_indices.size() == 1) && (num_draws == 1)) + { + int64_t trait_index = trait_indices[0]; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(DrawEffectForTrait(trait_index))); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size() * num_draws); + + // draw_index is the outer loop, so that we get num_draws sets of (one draw per trait) + for (int64_t draw_index = 0; draw_index < num_draws; ++draw_index) + for (int64_t trait_index : trait_indices) + float_result->push_float_no_check(DrawEffectForTrait(trait_index)); + + return EidosValue_SP(float_result); + } +} + +// ********************* - (void)setDefaultDominanceForTrait(Nio trait, float dominance) +// +EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *dominance_value = p_arguments[1].get(); + int dominance_count = dominance_value->Count(); - for (int64_t draw_index = 0; draw_index < num_draws; ++draw_index) - float_result->set_float_no_check(DrawSelectionCoefficient(), draw_index); + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDefaultDominanceForTrait"); - return result_SP; + if (dominance_count == 1) + { + // get the dominance coefficient + double dominance = dominance_value->FloatAtIndex_NOCAST(0, nullptr); + + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &de_info = effect_distributions_[trait_index]; + + de_info.default_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check + } + } + else if (dominance_count == (int)trait_indices.size()) + { + for (int dominance_index = 0; dominance_index < dominance_count; dominance_index++) + { + int64_t trait_index = trait_indices[dominance_index]; + EffectDistributionInfo &de_info = effect_distributions_[trait_index]; + double dominance = dominance_value->FloatAtIndex_NOCAST(dominance_index, nullptr); + + de_info.default_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check + } + } + else + EIDOS_TERMINATION << "ERROR (ExecuteMethod_setDefaultDominanceForTrait): setDefaultDominanceForTrait() requires parameter dominance to be of length 1, or equal in length to the number of specified traits." << EidosTerminate(nullptr); + + // BCH 7/2/2025: Changing the default dominance coefficient no longer means that the cached fitness + // effects of all mutations using this type become invalid; it is now just the *default* coefficient, + // and changing it does not change the state of mutations that have already derived from it. We do + // still want to let the community know that a mutation type has changed, though. + //species_.any_dominance_coeff_changed_ = true; + species_.community_.mutation_types_changed_ = true; + + return gStaticEidosValueVOID; } -// ********************* - (void)setDistribution(string$ distributionType, ...) +// ********************* - (void)setDistributionForTrait(Nio trait, string$ distributionType, ...) // -EidosValue_SP MutationType::ExecuteMethod_setDistribution(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_setDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) - EidosValue *distributionType_value = p_arguments[0].get(); + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *distributionType_value = p_arguments[1].get(); std::string dfe_type_string = distributionType_value->StringAtIndex_NOCAST(0, nullptr); + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDefaultDominanceForTrait"); + // Parse the DFE type and parameters, and do various sanity checks DFEType dfe_type; std::vector dfe_parameters; std::vector dfe_strings; - MutationType::ParseDFEParameters(dfe_type_string, p_arguments.data() + 1, (int)p_arguments.size() - 1, &dfe_type, &dfe_parameters, &dfe_strings); + MutationType::ParseDFEParameters(dfe_type_string, p_arguments.data() + 2, (int)p_arguments.size() - 2, &dfe_type, &dfe_parameters, &dfe_strings); // keep track of whether we have ever seen a type 's' (scripted) DFE; if so, we switch to a slower case when evolving if (dfe_type == DFEType::kScript) species_.type_s_dfes_present_ = true; - // Everything seems to be in order, so replace our distribution info with the new info - dfe_type_ = dfe_type; - dfe_parameters_ = dfe_parameters; - dfe_strings_ = dfe_strings; + // Everything seems to be in order, so replace our distribution info (in each specified trait) with the new info + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &de_info = effect_distributions_[trait_index]; + + de_info.dfe_type_ = dfe_type; + de_info.dfe_parameters_ = dfe_parameters; + de_info.dfe_strings_ = dfe_strings; + } // mark that mutation types changed, so they get redisplayed in SLiMgui species_.community_.mutation_types_changed_ = true; // check whether we are now using a DFE type that is non-neutral; check and set pure_neutral_ and all_pure_neutral_DFE_ - if ((dfe_type_ != DFEType::kFixed) || (dfe_parameters_[0] != 0.0)) + if ((dfe_type != DFEType::kFixed) || (dfe_parameters[0] != 0.0)) { species_.pure_neutral_ = false; all_pure_neutral_DFE_ = false; @@ -821,9 +980,6 @@ const std::vector *MutationType_Class::Properties(vo properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(MutationType::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_convertToSubstitution, false, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedSet(MutationType::SetProperty_Accelerated_convertToSubstitution)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_distributionType, true, kEidosValueMaskString | kEidosValueMaskSingleton))); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_distributionParams, true, kEidosValueMaskFloat | kEidosValueMaskString))); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominanceCoeff, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(MutationType::GetProperty_Accelerated_dominanceCoeff)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominanceCoeff, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationStackGroup, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationStackPolicy, false, kEidosValueMaskString | kEidosValueMaskSingleton))); @@ -849,8 +1005,12 @@ const std::vector *MutationType_Class::Methods(void) c methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawSelectionCoefficient, kEidosValueMaskFloat))->AddInt_OS("n", gStaticEidosValue_Integer1)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDistribution, kEidosValueMaskVOID))->AddString_S("distributionType")->AddEllipsis()); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultDominanceForTrait, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_distributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_distributionTypeForTrait, kEidosValueMaskString | kEidosValueMaskSingleton))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDistributionForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } @@ -918,6 +1078,5 @@ const std::vector *MutationType_Class::Methods(void) c - diff --git a/core/mutation_type.h b/core/mutation_type.h index 122ed7fd..563d8bee 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -59,6 +59,17 @@ enum class DFEType : char { std::ostream& operator<<(std::ostream& p_out, DFEType p_dfe_type); + +// This struct holds information about a distribution of effects (including dominance) for one trait. +// MutationEffect then keeps a vector of these structs, one for each trait. +typedef struct _EffectDistributionInfo { + slim_selcoeff_t default_dominance_coeff_; // the default dominance coefficient (h) inherited by mutations of this type + + DFEType dfe_type_; // distribution of fitness effects (DFE) type (f: fixed, g: gamma, e: exponential, n: normal, w: Weibull) + std::vector dfe_parameters_; // DFE parameters, of type double (originally float or integer type) + std::vector dfe_strings_; // DFE parameters, of type std::string (originally string type) +} EffectDistributionInfo; + class MutationType : public EidosDictionaryUnretained { @@ -72,9 +83,9 @@ class MutationType : public EidosDictionaryUnretained public: - // a mutation type is specified by the DFE and the dominance coefficient + // a mutation type is specified by the distribution of effects (DE) and the default dominance coefficient // - // DFE options: f: fixed (s) + // DE options: f: fixed (s) // e: exponential (mean s) // g: gamma distribution (mean s,shape) // @@ -85,12 +96,9 @@ class MutationType : public EidosDictionaryUnretained slim_objectid_t mutation_type_id_; // the id by which this mutation type is indexed in the chromosome EidosValue_SP cached_value_muttype_id_; // a cached value for mutation_type_id_; reset() if that changes - slim_selcoeff_t default_dominance_coeff_; // the default dominance coefficient (h) inherited by mutations of this type - slim_selcoeff_t hemizygous_dominance_coeff_; // dominance coefficient (h) used when one haplosome is null + std::vector effect_distributions_; // DEs for each trait in the species - DFEType dfe_type_; // distribution of fitness effects (DFE) type (f: fixed, g: gamma, e: exponential, n: normal, w: Weibull) - std::vector dfe_parameters_; // DFE parameters, of type double (originally float or integer type) - std::vector dfe_strings_; // DFE parameters, of type std::string (originally string type) + slim_selcoeff_t hemizygous_dominance_coeff_; // dominance coefficient (h) used when one haplosome is null bool nucleotide_based_; // if true, the mutation type is nucleotide-based (i.e. mutations keep associated nucleotides) @@ -172,7 +180,9 @@ class MutationType : public EidosDictionaryUnretained static void ParseDFEParameters(std::string &p_dfe_type_string, const EidosValue_SP *const p_arguments, int p_argument_count, DFEType *p_dfe_type, std::vector *p_dfe_parameters, std::vector *p_dfe_strings); - double DrawSelectionCoefficient(void) const; // draw a selection coefficient from this mutation type's DFE + double DrawEffectForTrait(int64_t p_trait_index) const; // draw a selection coefficient from the DE for a trait + + bool IsPureNeutralDFE(void) const { return all_pure_neutral_DFE_; } // // Eidos support @@ -186,8 +196,12 @@ class MutationType : public EidosDictionaryUnretained virtual void SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) override; virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; - EidosValue_SP ExecuteMethod_drawSelectionCoefficient(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_setDistribution(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_defaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_distributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_distributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_setDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); diff --git a/core/slim_functions.cpp b/core/slim_functions.cpp index be51bacb..27c4c413 100644 --- a/core/slim_functions.cpp +++ b/core/slim_functions.cpp @@ -510,7 +510,7 @@ R"V0G0N({ // get h for each mutation; note that this will not work if changing // h using mutationEffect() callbacks or other scripted approaches - h = muts.mutationType.dominanceCoeff; + h = muts.dominanceCoeff; // calculate number of haploid lethal equivalents (B or inbreeding load) // this equation is from Morton et al. 1956 diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 5b098814..9685b041 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1253,8 +1253,9 @@ const std::string &gStr_position = EidosRegisteredString("position", gID_positio const std::string &gStr_selectionCoeff = EidosRegisteredString("selectionCoeff", gID_selectionCoeff); const std::string &gStr_subpopID = EidosRegisteredString("subpopID", gID_subpopID); const std::string &gStr_convertToSubstitution = EidosRegisteredString("convertToSubstitution", gID_convertToSubstitution); -const std::string &gStr_distributionType = EidosRegisteredString("distributionType", gID_distributionType); -const std::string &gStr_distributionParams = EidosRegisteredString("distributionParams", gID_distributionParams); +const std::string &gStr_defaultDominanceForTrait = EidosRegisteredString("defaultDominanceForTrait", gID_defaultDominanceForTrait); +const std::string &gStr_distributionTypeForTrait = EidosRegisteredString("distributionTypeForTrait", gID_distributionTypeForTrait); +const std::string &gStr_distributionParamsForTrait = EidosRegisteredString("distributionParamsForTrait", gID_distributionParamsForTrait); const std::string &gStr_dominanceCoeff = EidosRegisteredString("dominanceCoeff", gID_dominanceCoeff); const std::string &gStr_hemizygousDominanceCoeff = EidosRegisteredString("hemizygousDominanceCoeff", gID_hemizygousDominanceCoeff); const std::string &gStr_mutationStackGroup = EidosRegisteredString("mutationStackGroup", gID_mutationStackGroup); @@ -1378,8 +1379,9 @@ const std::string &gStr_setMutationMatrix = EidosRegisteredString("setMutationMa const std::string &gStr_setSelectionCoeff = EidosRegisteredString("setSelectionCoeff", gID_setSelectionCoeff); const std::string &gStr_setDominanceCoeff = EidosRegisteredString("setDominanceCoeff", gID_setDominanceCoeff); const std::string &gStr_setMutationType = EidosRegisteredString("setMutationType", gID_setMutationType); -const std::string &gStr_drawSelectionCoefficient = EidosRegisteredString("drawSelectionCoefficient", gID_drawSelectionCoefficient); -const std::string &gStr_setDistribution = EidosRegisteredString("setDistribution", gID_setDistribution); +const std::string &gStr_drawEffectForTrait = EidosRegisteredString("drawEffectForTrait", gID_drawEffectForTrait); +const std::string &gStr_setDefaultDominanceForTrait = EidosRegisteredString("setDefaultDominanceForTrait", gID_setDefaultDominanceForTrait); +const std::string &gStr_setDistributionForTrait = EidosRegisteredString("setDistributionForTrait", gID_setDistributionForTrait); const std::string &gStr_addPatternForClone = EidosRegisteredString("addPatternForClone", gID_addPatternForClone); const std::string &gStr_addPatternForCross = EidosRegisteredString("addPatternForCross", gID_addPatternForCross); const std::string &gStr_addPatternForNull = EidosRegisteredString("addPatternForNull", gID_addPatternForNull); diff --git a/core/slim_globals.h b/core/slim_globals.h index b618dca3..eb41731f 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -833,8 +833,9 @@ extern const std::string &gStr_position; extern const std::string &gStr_selectionCoeff; extern const std::string &gStr_subpopID; extern const std::string &gStr_convertToSubstitution; -extern const std::string &gStr_distributionType; -extern const std::string &gStr_distributionParams; +extern const std::string &gStr_defaultDominanceForTrait; +extern const std::string &gStr_distributionTypeForTrait; +extern const std::string &gStr_distributionParamsForTrait; extern const std::string &gStr_dominanceCoeff; extern const std::string &gStr_hemizygousDominanceCoeff; extern const std::string &gStr_mutationStackGroup; @@ -957,8 +958,9 @@ extern const std::string &gStr_setMutationMatrix; extern const std::string &gStr_setSelectionCoeff; extern const std::string &gStr_setDominanceCoeff; extern const std::string &gStr_setMutationType; -extern const std::string &gStr_drawSelectionCoefficient; -extern const std::string &gStr_setDistribution; +extern const std::string &gStr_drawEffectForTrait; +extern const std::string &gStr_setDefaultDominanceForTrait; +extern const std::string &gStr_setDistributionForTrait; extern const std::string &gStr_addPatternForClone; extern const std::string &gStr_addPatternForCross; extern const std::string &gStr_addPatternForNull; @@ -1299,8 +1301,9 @@ enum _SLiMGlobalStringID : int { gID_selectionCoeff, gID_subpopID, gID_convertToSubstitution, - gID_distributionType, - gID_distributionParams, + gID_defaultDominanceForTrait, + gID_distributionTypeForTrait, + gID_distributionParamsForTrait, gID_dominanceCoeff, gID_hemizygousDominanceCoeff, gID_mutationStackGroup, @@ -1423,8 +1426,9 @@ enum _SLiMGlobalStringID : int { gID_setSelectionCoeff, gID_setDominanceCoeff, gID_setMutationType, - gID_drawSelectionCoefficient, - gID_setDistribution, + gID_drawEffectForTrait, + gID_setDefaultDominanceForTrait, + gID_setDistributionForTrait, gID_addPatternForClone, gID_addPatternForCross, gID_addPatternForNull, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index f9d2c63c..a471e16a 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -39,9 +39,9 @@ void _RunMutationTypeTests(void) SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.convertToSubstitution == T) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.mutationStackGroup == 1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.mutationStackPolicy == 's') stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.distributionParams == 0.0) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.distributionType == 'f') stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.dominanceCoeff == 0.5) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.distributionParamsForTrait() == 0.0) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.distributionTypeForTrait() == 'f') stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.defaultDominanceForTrait() == 0.5) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.id == 1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.color = ''; } 2 early() { if (m1.color == '') stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.color = 'red'; } 2 early() { if (m1.color == 'red') stop(); }", __LINE__); @@ -58,9 +58,6 @@ void _RunMutationTypeTests(void) SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.mutationStackPolicy = 'f'; }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.mutationStackPolicy = 'l'; }", __LINE__); SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.mutationStackPolicy = 'z'; }", "property mutationStackPolicy must be", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.distributionParams = 0.1; }", "read-only property", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.distributionType = 'g'; }", "read-only property", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.dominanceCoeff = 0.3; }", __LINE__); SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.id = 2; }", "read-only property", __LINE__); SLiMAssertScriptStop(gen1_setup + "initialize() { initializeMutationType('m2', 0.7, 'e', 0.5); c(m1,m2).mutationStackGroup = 3; c(m1,m2).mutationStackPolicy = 'f'; } 1 early() { stop(); }", __LINE__); @@ -70,78 +67,81 @@ void _RunMutationTypeTests(void) SLiMAssertScriptRaise(gen1_setup + "initialize() { initializeMutationType('m2', 0.7, 'e', 0.5); m1.mutationStackPolicy = 'f'; m2.mutationStackPolicy = 'l'; } 1 early() { c(m1,m2).mutationStackGroup = 3; }", "inconsistent mutationStackPolicy", __LINE__, false); // Test MutationType - (void)setDistribution(string$ distributionType, ...) - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('f', 2.2); if (m1.distributionType == 'f' & m1.distributionParams == 2.2) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('g', 3.1, 7.5); if (m1.distributionType == 'g' & identical(m1.distributionParams, c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('e', -3); if (m1.distributionType == 'e' & m1.distributionParams == -3) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('n', 3.1, 7.5); if (m1.distributionType == 'n' & identical(m1.distributionParams, c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('p', 3.1, 7.5); if (m1.distributionType == 'p' & identical(m1.distributionParams, c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('w', 3.1, 7.5); if (m1.distributionType == 'w' & identical(m1.distributionParams, c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('s', 'return 1;'); if (m1.distributionType == 's' & identical(m1.distributionParams, 'return 1;')) stop(); }", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('x', 1.5); stop(); }", "must be 'f', 'g', 'e', 'n', 'w', or 's'", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('f', 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('g', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('g', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('e', 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('n', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('n', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('p', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('p', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('s', 3); stop(); }", "must be of type string", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('f', '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('g', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('g', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('e', '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('n', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('n', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('p', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('p', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('s', 3.1); stop(); }", "must be of type string", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('f', T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('g', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('g', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('e', T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('n', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('n', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('p', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('p', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('s', T); stop(); }", "must be of type string", __LINE__); - - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('g', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('g', 3.1, -1.0); }", "must have a shape parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('n', 3.1, -1.0); }", "must have a standard deviation parameter >= 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('p', 3.1, 0.0); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('p', 3.1, -1.0); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', 0.0, 7.5); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', -1.0, 7.5); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistribution('w', 3.1, -7.5); }", "must have a shape parameter > 0", __LINE__); - - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setDistribution('s', 'return foo;'); } 100 early() { stop(); }", "undefined identifier foo", __LINE__, false); - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setDistribution('s', 'x >< 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DFE callback script", __LINE__, false); - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setDistribution('s', 'x $ 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DFE callback script", __LINE__, false); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultDominanceForTrait(NULL, 0.3); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultDominanceForTrait(0, 0.3); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultDominanceForTrait(sim.traits, 0.3); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', 2.2); if (m1.distributionTypeForTrait() == 'f' & m1.distributionParamsForTrait() == 2.2) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 7.5); if (m1.distributionTypeForTrait() == 'g' & identical(m1.distributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', -3); if (m1.distributionTypeForTrait() == 'e' & m1.distributionParamsForTrait() == -3) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, 7.5); if (m1.distributionTypeForTrait() == 'n' & identical(m1.distributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 7.5); if (m1.distributionTypeForTrait() == 'p' & identical(m1.distributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 7.5); if (m1.distributionTypeForTrait() == 'w' & identical(m1.distributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 'return 1;'); if (m1.distributionTypeForTrait() == 's' & identical(m1.distributionParamsForTrait(), 'return 1;')) stop(); }", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'x', 1.5); stop(); }", "must be 'f', 'g', 'e', 'n', 'w', or 's'", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 3); stop(); }", "must be of type string", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 3.1); stop(); }", "must be of type string", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', T); stop(); }", "must be of type string", __LINE__); + + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, -1.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, -1.0); }", "must have a standard deviation parameter >= 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 0.0); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, -1.0); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 0.0, 7.5); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', -1.0, 7.5); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, -7.5); }", "must have a shape parameter > 0", __LINE__); + + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setDistributionForTrait(NULL, 's', 'return foo;'); } 100 early() { stop(); }", "undefined identifier foo", __LINE__, false); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setDistributionForTrait(NULL, 's', 'x >< 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DFE callback script", __LINE__, false); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setDistributionForTrait(NULL, 's', 'x $ 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DFE callback script", __LINE__, false); // Test MutationType - (float)drawSelectionCoefficient([integer$ n = 1]) // the parameters here are chosen so that these tests should fail extremely rarely - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('f', 2.2); if (m1.drawSelectionCoefficient() == 2.2) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('f', 2.2); if (identical(m1.drawSelectionCoefficient(10), rep(2.2, 10))) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistribution('g', 3.1, 7.5); m1.drawSelectionCoefficient(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('g', 3.1, 7.5); if (abs(mean(m1.drawSelectionCoefficient(5000)) - 3.1) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistribution('e', -3.0); m1.drawSelectionCoefficient(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('e', -3.0); if (abs(mean(m1.drawSelectionCoefficient(30000)) + 3.0) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistribution('n', 3.1, 0.5); m1.drawSelectionCoefficient(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('n', 3.1, 0.5); if (abs(mean(m1.drawSelectionCoefficient(2000)) - 3.1) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistribution('p', 3.1, 7.5); m1.drawSelectionCoefficient(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('p', 3.1, 0.01); if (abs(mean(m1.drawSelectionCoefficient(2000)) - 3.1) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistribution('w', 3.1, 7.5); m1.drawSelectionCoefficient(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('w', 3.1, 7.5); if (abs(mean(m1.drawSelectionCoefficient(2000)) - 2.910106) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistribution('s', 'rbinom(1, 4, 0.5);'); m1.drawSelectionCoefficient(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistribution('s', 'rbinom(1, 4, 0.5);'); if (abs(mean(m1.drawSelectionCoefficient(5000)) - 2.0) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', 2.2); if (m1.drawEffectForTrait() == 2.2) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', 2.2); if (identical(m1.drawEffectForTrait(NULL, 10), rep(2.2, 10))) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', -3.0); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', -3.0); if (abs(mean(m1.drawEffectForTrait(NULL, 30000)) + 3.0) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, 0.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, 0.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 0.01); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 2.910106) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 2.0) < 0.1) stop(); }", __LINE__); } #pragma mark GenomicElementType tests diff --git a/core/species.cpp b/core/species.cpp index b636901a..7854dc56 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -571,6 +571,82 @@ void Species::AddTrait(Trait *p_trait) trait_from_string_id.emplace(name_string_id, p_trait); } +// This returns the trait index for a single trait, represented by an EidosValue with an integer index or a Trait object +int64_t Species::GetTraitIndexFromEidosValue(EidosValue *trait_value, const std::string &p_method_name) +{ + int64_t trait_index; + + if (trait_value->Type() == EidosValueType::kValueInt) + { + trait_index = trait_value->IntAtIndex_NOCAST(0, nullptr); + } + else + { + const Trait *trait = (const Trait *)trait_value->ObjectElementAtIndex_NOCAST(0, nullptr); + + if (&trait->species_ != this) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): " << p_method_name << "() requires trait to belong to the same species as the target mutation type." << EidosTerminate(nullptr); + + trait_index = trait->Index(); + } + + if ((trait_index < 0) || (trait_index >= TraitCount())) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): out-of-range trait index in " << p_method_name << "(); trait index " << trait_index << " is outside the range [0, " << (TraitCount() - 1) << "] for the species." << EidosTerminate(nullptr); + + return trait_index; +} + +// This returns trait indices, represented by an EidosValue with integer indices or Trait objects, or NULL for all traits +void Species::GetTraitIndicesFromEidosValue(std::vector &trait_indices, EidosValue *traits_value, const std::string &p_method_name) +{ + EidosValueType traits_value_type = traits_value->Type(); + int traits_value_count = traits_value->Count(); + int64_t trait_count = TraitCount(); + + switch (traits_value_type) + { + // NULL means "all traits", unlike for GetTraitIndexFromEidosValue() + case EidosValueType::kValueNULL: + { + for (int64_t trait_index = 0; trait_index < trait_count; ++trait_index) + trait_indices.push_back(trait_index); + break; + } + case EidosValueType::kValueInt: + { + const int64_t *indices_data = traits_value->IntData(); + + for (int indices_index = 0; indices_index < traits_value_count; indices_index++) + { + int64_t trait_index = indices_data[indices_index]; + + if ((trait_index < 0) || (trait_index >= TraitCount())) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndicesFromEidosValue): out-of-range trait index in " << p_method_name << "(); trait index " << trait_index << " is outside the range [0, " << (TraitCount() - 1) << "] for the species." << EidosTerminate(nullptr); + + trait_indices.push_back(trait_index); + } + break; + } + case EidosValueType::kValueObject: + { + Trait * const *traits_data = (Trait * const *)traits_value->ObjectData(); + + for (int traits_index = 0; traits_index < traits_value_count; ++traits_index) + { + Trait *trait = traits_data[traits_index]; + + if (&trait->species_ != this) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndicesFromEidosValue): " << p_method_name << "() requires trait to belong to the same species as the target mutation type." << EidosTerminate(nullptr); + + trait_indices.push_back(trait->Index()); + } + break; + } + default: + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndicesFromEidosValue): (internal error) unexpected type for parameter trait." << EidosTerminate(); + } +} + // get one line of input, sanitizing by removing comments and whitespace; used only by Species::InitializePopulationFromTextFile void GetInputLine(std::istream &p_input_file, std::string &p_line); void GetInputLine(std::istream &p_input_file, std::string &p_line) @@ -2701,7 +2777,7 @@ void Species::RunInitializeCallbacks(void) for (auto muttype : getype->mutation_type_ptrs_) { - if ((muttype->dfe_type_ == DFEType::kFixed) && (muttype->dfe_parameters_.size() == 1) && (muttype->dfe_parameters_[0] == 0.0)) + if (muttype->IsPureNeutralDFE()) using_neutral_muttype = true; } } @@ -9707,7 +9783,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapdefault_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); + Substitution *sub = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); // FIXME MULTITRAIT population_.treeseq_substitutions_map_.emplace(position, sub); population_.substitutions_.emplace_back(sub); @@ -9721,7 +9797,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapdefault_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); + Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); // FIXME MULTITRAIT // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry p_mutIndexMap[mutation_id] = new_mut_index; diff --git a/core/species.h b/core/species.h index 4ce8a48a..9956259b 100644 --- a/core/species.h +++ b/core/species.h @@ -442,11 +442,15 @@ class Species : public EidosDictionaryUnretained // Trait configuration and access inline __attribute__((always_inline)) const std::vector &Traits(void) { return traits_; } + inline __attribute__((always_inline)) int64_t TraitCount(void) { return (int64_t)traits_.size(); } Trait *TraitFromName(const std::string &p_name); Trait *TraitFromStringID(EidosGlobalStringID p_string_id); void MakeImplicitTrait(void); void AddTrait(Trait *p_trait); // takes over a retain count from the caller + int64_t GetTraitIndexFromEidosValue(EidosValue *trait_value, const std::string &p_method_name); // with a singleton EidosValue + void GetTraitIndicesFromEidosValue(std::vector &trait_indices, EidosValue *traits_value, const std::string &p_method_name); + // Memory usage void TabulateSLiMMemoryUsage_Species(SLiMMemoryUsage_Species *p_usage); // used by outputUsage() and SLiMgui profiling void DeleteAllMutationRuns(void); // for cleanup diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 6cbc5b10..6d8dd875 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -528,11 +528,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const mutation_fractions.emplace_back(proportion); // check whether we are using a mutation type that is non-neutral; check and set pure_neutral_ - if ((mutation_type_ptr->dfe_type_ != DFEType::kFixed) || (mutation_type_ptr->dfe_parameters_[0] != 0.0)) - { + if (!mutation_type_ptr->IsPureNeutralDFE()) pure_neutral_ = false; - // the mutation type's all_pure_neutral_DFE_ flag is presumably already set - } } EidosValueType mm_type = mutationMatrix_value->Type(); diff --git a/eidos/eidos_globals.h b/eidos/eidos_globals.h index 31fef121..9f6a7c8e 100644 --- a/eidos/eidos_globals.h +++ b/eidos/eidos_globals.h @@ -1219,7 +1219,7 @@ enum _EidosGlobalStringID : uint32_t gEidosID_Individual, gEidosID_LastEntry, // IDs added by the Context should start here - gEidosID_LastContextEntry = 545 // IDs added by the Context must end before this value; Eidos reserves the remaining values + gEidosID_LastContextEntry = 555 // IDs added by the Context must end before this value; Eidos reserves the remaining values }; extern std::vector gEidosConstantNames; // T, F, NULL, PI, E, INF, NAN diff --git a/eidos/eidos_value.cpp b/eidos/eidos_value.cpp index 363b4973..cb6ee67c 100644 --- a/eidos/eidos_value.cpp +++ b/eidos/eidos_value.cpp @@ -2296,6 +2296,7 @@ EidosValue_SP EidosValue_Object::GetPropertyOfElements(EidosGlobalStringID p_pro void EidosValue_Object::SetPropertyOfElements(EidosGlobalStringID p_property_id, const EidosValue &p_value, EidosToken *p_property_token) { +#pragma unused(p_property_token) const EidosPropertySignature *signature = class_->SignatureForProperty(p_property_id); // BCH 6/29/2025: To enable the special trait properties of Individual, we now allow From 814deccfea06cf97db205f9bc0114178b1185957 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 9 Oct 2025 13:06:01 -0400 Subject: [PATCH 004/107] finish the merge of master into multitrait (fix doc and project file) --- QtSLiM/help/SLiMHelpClasses.html | 42 +++++++++--------- QtSLiM/help/SLiMHelpFunctions.html | 6 +-- SLiM.xcodeproj/project.pbxproj | 28 ++++++------ SLiMgui/SLiMHelpClasses.rtf | 68 +++++++++++++++--------------- SLiMgui/SLiMHelpFunctions.rtf | 19 ++++----- VERSIONS | 61 ++++++++++++++------------- 6 files changed, 113 insertions(+), 111 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index e9ebb4e9..0c93ad57 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -21,6 +21,7 @@ p.p12 {margin: 6.0px 0.0px 3.0px 0.0px; font: 11.0px Optima; color: #000000} p.p13 {margin: 2.0px 0.0px 2.0px 0.0px; text-indent: 13.7px; font: 11.0px 'Times New Roman'; min-height: 12.0px} p.p14 {margin: 3.0px 0.0px 3.0px 27.4px; font: 9.0px Menlo} + p.p15 {margin: 0.0px 0.0px 3.0px 0.0px; font: 11.0px Optima; color: #000000; min-height: 13.0px} span.s1 {font: 9.0px Menlo} span.s2 {font: 10.0px 'Times New Roman'} span.s3 {font-kerning: none} @@ -35,9 +36,10 @@ span.s12 {text-decoration: underline ; color: #0000ff} span.s13 {font: 6.7px 'Times New Roman'} span.s14 {font: 10.0px 'Lucida Grande'} - span.s15 {font: 6.7px Optima} - span.s16 {font: 10.0px Optima; color: #000000} - span.s17 {font: 6.7px Optima; font-kerning: none} + span.s15 {font: 10.0px 'Times New Roman'; color: #000000} + span.s16 {font: 6.7px Optima} + span.s17 {font: 10.0px Optima; color: #000000} + span.s18 {font: 6.7px Optima; font-kerning: none} span.Apple-tab-span {white-space:pre} @@ -682,9 +684,9 @@

The tick in which this mutation arose.

position => (integer$)

The position in the chromosome of this mutation.

-

selectionCoeff => (float$)

-

The selection coefficient of the mutation, drawn from the distribution of fitness effects of its MutationType.  If a mutation has a selectionCoeff of s and a dominanceCoeff of h, the multiplicative fitness effect of the mutation in a homozygote is 1+s, and in a heterozygote is 1+hs.  The selection coefficient of a mutation can be changed with the setSelectionCoeff() method.

-

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s selection coefficient to some number x, mut.selectionCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutations.

+

selectionCoeff => (float$)

+

The selection coefficient of the mutation, drawn from the distribution of fitness effects of its MutationType.  If a mutation has a selectionCoeff of s, the multiplicative fitness effect of the mutation in a homozygote is 1+s; in a heterozygote it is 1+hs, where h is the dominance coefficient kept by the mutation type.

+

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s selection coefficient to some number x, mut.selectionCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutations.

subpopID <–> (integer$)

The identifier of the subpopulation in which this mutation arose.  This property can be used to track the ancestry of mutations through their subpopulation of origin.

If you don’t care which subpopulation a mutation originated in, the subpopID may be used as an arbitrary integer “tag” value for any purpose you wish; SLiM does not do anything with the value of subpopID except propagate it to Substitution objects and report it in output.  (It must still be >= 0, however, since SLiM object identifiers are limited to nonnegative integers).

@@ -694,9 +696,9 @@

– (void)setDominanceCoeff(float$ dominanceCoeff)

Set the dominance coefficient of the mutation to dominanceCoeff.  The dominance coefficient will be changed for all individuals that possess the mutation, since they all share a single Mutation object (note that the selection coefficient will remain unchanged).

Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

-

– (void)setMutationType(io<MutationType>$ mutType)

+

– (void)setMutationType(io<MutationType>$ mutType)

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  The selection coefficients and dominance coefficients of existing mutations are not changed, since they are properties of the mutation objects themselves; they can be changed explicitly using the setSelectionCoeff() and setDominanceCoeff() methods of Mutation if so desired.

-

In nucleotide-based models, a restriction applies: nucleotide-based mutations may not be changed to a non-nucleotide-based mutation type, and non-nucleotide-based mutations may not be changed to a nucleotide-based mutation type.

+

In nucleotide-based models, a restriction applies: nucleotide-based mutations may not be changed to a non-nucleotide-based mutation type, and non-nucleotide-based mutations may not be changed to a nucleotide-based mutation type.

– (void)setSelectionCoeff(float$ selectionCoeff)

Set the selection coefficient of the mutation to selectionCoeff.  The selection coefficient will be changed for all individuals that possess the mutation, since they all share a single Mutation object (note that the dominance coefficient will remain unchanged).

Often setting up a mutationEffect() callback is preferable, in order to modify the selection coefficient in a more limited and controlled fashion.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

@@ -712,7 +714,7 @@

In contrast, for nonWF models this property is F by default, because even mutations with no epistatis or other indirect fitness effects will continue to influence the survival probabilities of individuals.  For nonWF models, only neutral mutation types with no epistasis or other side effects can safely be converted to substitutions upon fixation.  When such a pure-neutral mutation type is defined in a nonWF model, this property should be set to T to tell SLiM that substitution is allowed; this may have very large positive effects on performance, so it is important to remember when modeling background neutral mutations.

SLiM consults this flag at the end of each tick when deciding whether to substitute each fixed mutation.  If this flag is T, all eligible fixed mutations will be converted at the end of the current tick, even if they were previously left unconverted because of the previous value of the flag.  Setting this flag to F will prevent future substitutions, but will not cause any existing Substitution objects to be converted back into Mutation objects.

hemizygousDominanceCoeff <–> (float$)

-

The dominance coefficient used for mutations of this type when they occur opposite a null haplosome (as can occur in sex-chromosome models and models involving a mix of haploids and diploids).  This defaults to 1.0, and is used only in models where null haplosomes are present; the dominanceCoeff property of the mutation is the dominance coefficient used in most circumstances.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

+

The dominance coefficient used for mutations of this type when they occur opposite a null haplosome (as can occur in sex-chromosome models and models involving a mix of haploids and diploids).  This defaults to 1.0, and is used only in models where null haplosomes are present; the dominanceCoeff property is the dominance coefficient used in most circumstances.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

As with the dominanceCoeff property, this is stored internally using a single-precision float; see the documentation for dominanceCoeff for discussion.

id => (integer$)

The identifier for this mutation type; for mutation type m3, for example, this is 3.

@@ -879,7 +881,7 @@

– (object<SpatialMap>$)divide(ifo<SpatialMap> x)

Divides the spatial map by x.  One possibility is that x is a singleton integer or float value; in this case, each grid value of the target spatial map is divided by x.  Another possibility is that x is an integer or float vector/matrix/array of the same dimensions as the target spatial map’s grid; in this case, each grid value of the target spatial map is divided by the corresponding value of x.  The third possibility is that x is itself a (singleton) spatial map; in this case, each grid value of the target spatial map is divided by the corresponding grid value of x (and thus the two spatial maps must match in their spatiality, their spatial bounds, and their grid dimensions).  The target spatial map is returned, to allow easy chaining of operations.

– (object<SpatialMap>$)exp(void)

-

Exponentiates the values of the spatial map.  More precisely, each grid value x of the target spatial map is exponentiated – replaced by the value ex.  The target spatial map is returned, to allow easy chaining of operations.

+

Exponentiates the values of the spatial map.  More precisely, each grid value x of the target spatial map is exponentiated – replaced by the value ex.  The target spatial map is returned, to allow easy chaining of operations.

– (float)gridValues(void)

Returns the values for the spatial map’s grid as a vector (for a 1D map), a matrix (for a 2D map), or an array (for a 3D map).  The form and orientation of the returned values is such that it could be used to create a new spatial map, with defineSpatialMap(), which would be identical to the original.

– (object<SpatialMap>$)interpolate(integer$ factor, [string$ method = "linear"])

@@ -1012,10 +1014,10 @@

Note that this method is only for use in nonWF models, in which mortality is managed manually by the model script.  In WF models, mortality is managed automatically by the SLiM core when the new offspring generation becomes the parental generation and the previous parental generation dies; mortality does not otherwise occur in WF models.  In nonWF models, mortality normally occurs during the survival stage of the tick cycle, based upon the fitness values calculated by SLiM, and survival() callbacks can influence the outcome of that survival stage.  Calls to killIndividuals(), on the other hand, can be made at any time during first(), early(), or late() events, and the result cannot be modified by survival() callbacks; the given individuals are simply immediately killed.  This method therefore provides an alternative, and relatively rarely used, mortality mechanism that is disconnected from fitness.

– (integer)mutationCounts(Nio<Subpopulation> subpops, [No<Mutation> mutations = NULL])

Return an integer vector with the frequency counts of all of the Mutation objects passed in mutations, within the Subpopulation objects in subpops.  The subpops argument is required, but you may pass NULL to get population-wide frequency counts.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects.  If the optional mutations argument is NULL (the default), frequency counts will be returned for all of the active Mutation objects in the species – the same Mutation objects, and in the same order, as would be returned by the mutations property of sim, in other words.

-

See the -mutationFrequencies() method to obtain float frequencies instead of integer counts.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

+

See the -mutationFrequencies() method to obtain float frequencies instead of integer counts.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

– (float)mutationFrequencies(Nio<Subpopulation> subpops, [No<Mutation> mutations = NULL])

Return a float vector with the frequencies of all of the Mutation objects passed in mutations, within the Subpopulation objects in subpops.  The subpops argument is required, but you may pass NULL to get population-wide frequencies.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects.  If the optional mutations argument is NULL (the default), frequencies will be returned for all of the active Mutation objects in the species – the same Mutation objects, and in the same order, as would be returned by the mutations property of sim, in other words.

-

See the -mutationCounts() method to obtain integer counts instead of float frequencies.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

+

See the -mutationCounts() method to obtain integer counts instead of float frequencies.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

 (object<Mutation>)mutationsOfType(io<MutationType>$ mutType)

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations that are currently active in the species.  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType().  This method is often used to look up an introduced mutation at a later point in the simulation, since there is no way to keep persistent references to objects in SLiM.  This method is provided for speed; it is much faster than the corresponding Eidos code.

– (void)outputFixedMutations([Ns$ filePath = NULL], [logical$ append = F], [logical$ objectTags = F])

@@ -1028,7 +1030,7 @@

Output the state of the entire population.  If the optional parameter filePath is NULL (the default), output will be sent to Eidos’s output stream.  Otherwise, output will be sent to the filesystem path specified by filePath, overwriting that file if append if F, or appending to the end of it if append is T.  When writing to a file, a logical flag, binary, may be supplied as well.  If binary is T, the population state will be written as a binary file instead of a text file (binary data cannot be written to the standard output stream).  The binary file is usually smaller, and in any case will be read much faster than the corresponding text file would be read.  Binary files are not guaranteed to be portable between platforms; in other words, a binary file written on one machine may not be readable on a different machine (but in practice it usually will be, unless the platforms being used are fairly unusual).  If binary is F (the default), a text file will be written.

Beginning with SLiM 2.3, the spatialPositions parameter may be used to control the output of the spatial positions of individuals in species for which continuous space has been enabled using the dimensionality option of initializeSLiMOptions().  If spatialPositions is F, the output will not contain spatial positions, and will be identical to the output generated by SLiM 2.1 and later.  If spatialPositions is T, spatial position information will be output if it is available.  If the species does not have continuous space enabled, the spatialPositions parameter will be ignored.  Positional information may be output for all output destinations – the Eidos output stream, a text file, or a binary file.

Beginning with SLiM 3.0, the ages parameter may be used to control the output of the ages of individuals in nonWF simulations.  If ages is F, the output will not contain ages, preserving backward compatibility with the output format of SLiM 2.1 and later.  If ages is T, ages will be output for nonWF models.  In WF simulations, the ages parameter will be ignored.

-

Beginning with SLiM 3.3, the ancestralNucleotides parameter may be used to control the output of the ancestral nucleotide sequence in nucleotide-based models.  If ancestralNucleotides is F, the output will not contain ancestral nucleotide information, and so the ancestral sequence will not be restored correctly if the saved file is loaded with readPopulationFile().  This option is provided because the ancestral sequence may be quite large, for models with a long chromosome (e.g., 1 GB if the chromosome is 109 bases long, when saved in text format, or 0.25 GB when saved in binary format).  If the model is not nucleotide-based (as enabled with the nucleotideBased parameter to initializeSLiMOptions()), the ancestralNucleotides parameter will be ignored.  Note that in nucleotide-based models the output format will always include the nucleotides associated with any nucleotide-based mutations; the ancestralNucleotides flag governs only the ancestral sequence.

+

Beginning with SLiM 3.3, the ancestralNucleotides parameter may be used to control the output of the ancestral nucleotide sequence in nucleotide-based models.  If ancestralNucleotides is F, the output will not contain ancestral nucleotide information, and so the ancestral sequence will not be restored correctly if the saved file is loaded with readPopulationFile().  This option is provided because the ancestral sequence may be quite large, for models with a long chromosome (e.g., 1 GB if the chromosome is 109 bases long, when saved in text format, or 0.25 GB when saved in binary format).  If the model is not nucleotide-based (as enabled with the nucleotideBased parameter to initializeSLiMOptions()), the ancestralNucleotides parameter will be ignored.  Note that in nucleotide-based models the output format will always include the nucleotides associated with any nucleotide-based mutations; the ancestralNucleotides flag governs only the ancestral sequence.

Beginning with SLiM 3.5, the pedigreeIDs parameter may be used to request that pedigree IDs be written out (and read in by readFromPopulationFile(), subsequently).  This option is turned off (F) by default, for brevity.  This option may only be used if SLiM’s optional pedigree tracking has been enabled with initializeSLiMOptions(keepPedigrees=T).

Beginning with SLiM 5.0, the objectTags parameter may be used to request that tag values for objects be written out.  This option is turned off (F) by default, for brevity; if it turned on (T), the values of all tags for all objects of supported classes (Chromosome, Subpopulation, Individual, Haplosome, Mutation, Substitution) will be written.  For individuals, the tag, tagF, tagL0, tagL1, tagL2, tagL3, and tagL4 properties will be written; for chromosomes, subpopulations, haplosomes, and mutations, the tag property will be written.  The saved tag information can be read in by readFromPopulationFile(), but only if the output is in binary format (binary=T).  Note that if there is other state that you wish you persist, such as tags on objects of other classes, values attached to objects with setValue(), and so forth, you should persist that state in separate files using calls such as writeFile().

Beginning with SLiM 5.0, the substitutions parameter may be used to request that information about Substitution objects in the simulation be written out.  This option is turned off (F) by default, for brevity.  The saved substitution information can be read in by readFromPopulationFile(), but only if the output is in binary format (binary=T).

@@ -1256,7 +1258,7 @@

Returns a vector containing n points that are derived from point by adding a deviation drawn from a dispersal kernel (specified by maxDistance, functionType, and the ellipsis parameters ..., as detailed below) and then applying a boundary condition specified by boundary.  This method therefore performs the steps of a simple dispersal algorithm in a single vectorized call.  See deviatePositions() for an even more efficient approach.

The parameter point may contain a single point which is deviated and bounded n independent times, or may contain n points each of which is deviated and bounded.  In any case, each point in point should match the dimensionality of the model – one element in a 1D model, two elements in a 2D model, or three elements in a 3D model.  This method should not be called in a non-spatial model.

The dispersal kernel is specified similarly to other kernel-based methods, such as setInteractionFunction() and smooth().  For pointDeviated(), functionType may be "f" with no ellipsis arguments ... to use a flat kernel out to maxDistance; "l" with no ellipsis arguments for a kernel that decreases linearly from the center to zero at maxDistance; "e", in which case the ellipsis should supply a numeric$ lambda (rate) parameter for a negative exponential function; "n", in which case the ellipsis should supply a numeric$ sigma (standard deviation) parameter for a Gaussian function; or "t", in which case the ellipsis should supply a numeric$ degrees of freedom and a numeric$ scale parameter for a t-distribution function.  The Cauchy ("c") kernel is not supported by pointDeviated() since it is not well-behaved for this purpose, and the Student’s t ("t") kernel is not allowed in 3D models at present simply because it hasn’t been implemented.  See the InteractionType class documentation for more detailed discussion of the available kernel types and their parameters and probability distribution functions.  For pointDeviated(), the ellipsis parameters that follow functionType may each, independently, be either a singleton or a vector of length equal to n.  This allows each point to be deviated with a different kernel, representing, for example, the movements of individuals with differing dispersal capabilities/propensities.  (However, other parameters such as boundary, maxDistance, and functionType must be the same for all of the points, in the present design.)

-

The random points returned from this method are drawn from the probability distribution that is radially symmetric and has density proportional to the kernel – in other words, at distance r the density is proportional to the kernel type referred to by functionType.  (Said another way, the shape of the cross-section through the probability density function is given by the kernel.)  For instance, the value of the type "e" (exponential) kernel with rate a at r is proportional to exp(−ar), and so in 2D, the probability density that this method with kernel type "e" draws from has density proportional to p(xy) = exp(−a sqrt(x2 + y2)), since r = sqrt(x2 + y2) is the distance.  Note that the distribution of the distance is not given by the kernel except in 1D: in the type "e" example, the distribution of the distance in 1D is exponential, while in 2D it has density proportional to r exp(−ar) (i.e., Gamma with shape parameter 1).  For another example, the value of the type "n" (Normal) kernel at r with standard deviation 1 is proportional to exp(−r2 / 2), and so the density is proportional to p(xy) = exp(−(x2 + y2) / 2).  This is the standard bivariate Normal, and equivalent to drawing independent Normals for the x and y directions; however, the Normal is the only distribution for which independent draws along each axis will result in a radially symmetric distribution.  The distribution of the distance in 2D with type "n" is proportional to r exp(−r2 / 2), i.e., Rayleigh.

+

The random points returned from this method are drawn from the probability distribution that is radially symmetric and has density proportional to the kernel – in other words, at distance r the density is proportional to the kernel type referred to by functionType.  (Said another way, the shape of the cross-section through the probability density function is given by the kernel.)  For instance, the value of the type "e" (exponential) kernel with rate a at r is proportional to exp(−ar), and so in 2D, the probability density that this method with kernel type "e" draws from has density proportional to p(xy) = exp(−a sqrt(x2 + y2)), since r = sqrt(x2 + y2) is the distance.  Note that the distribution of the distance is not given by the kernel except in 1D: in the type "e" example, the distribution of the distance in 1D is exponential, while in 2D it has density proportional to r exp(−ar) (i.e., Gamma with shape parameter 1).  For another example, the value of the type "n" (Normal) kernel at r with standard deviation 1 is proportional to exp(−r2 / 2), and so the density is proportional to p(xy) = exp(−(x2 + y2) / 2).  This is the standard bivariate Normal, and equivalent to drawing independent Normals for the x and y directions; however, the Normal is the only distribution for which independent draws along each axis will result in a radially symmetric distribution.  The distribution of the distance in 2D with type "n" is proportional to r exp(−r2 / 2), i.e., Rayleigh.

The boundary condition must be one of "none", "periodic", "reflecting", "stopping", or "reprising".  For "none", no boundary condition is enforced; the deviated points are simply returned as is.  For "periodic", "reflecting", and "stopping", the boundary condition is enforced just as it is by the pointPeriodic(), pointReflected(), and pointStopped() methods; see their documentation for further details.  For "reprising", if the deviated point is out of bounds a new deviated point will be chosen, based upon the same original point, until a point inside bounds is obtained.  Note that absorbing boundaries (for which being out-of-bounds is lethal) would need to be implemented in script; this method cannot enforce them.  (Note, however, that the deviatePositions() method of Subpopulation can enforce absorbing boundaries.)

Note that for the typical usage case, in which point comes from the spatialPosition property for a vector of individuals, and the result is then set back onto the same vector of individuals using the setSpatialPosition() method, the deviatePositions() method provides an even more efficient alternative.

– (logical)pointInBounds(float point)

@@ -1331,16 +1333,16 @@

The tick in which this mutation arose.

position => (integer$)

The position in the chromosome of this mutation.

-

selectionCoeff => (float$)

-

The selection coefficient of the mutation, carried over from the original mutation object.

+

selectionCoeff => (float$)

+

The selection coefficient of the mutation, drawn from the distribution of fitness effects of its MutationType.

subpopID <–> (integer$)

The identifier of the subpopulation in which this mutation arose.  This value is carried over from the Mutation object directly; if a “tag” value was used in the Mutation object, that value will carry over to the corresponding Substitution object.  The subpopID in Substitution is a read-write property to allow it to be used as a “tag” in the same way, if the origin subpopulation identifier is not needed.

tag <–> (integer$)

A user-defined integer value.  The value of tag is carried over automatically from the original Mutation object.  Apart from that, the value of tag is not used by SLiM; it is free for you to use.

5.18.2  Substitution methods


-

5.19  Class Trait

-

5.19.1  Trait properties

+

5.19  Class Trait

+

5.19.1  Trait properties

baselineOffset <–> (float$)

The baseline offset for the trait.  This value is combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.

directFitnessEffect <–> (logical$)

@@ -1359,7 +1361,7 @@

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.

type => (string$)

The type of the trait, as a string.  In the present design, this will be either "multiplicative" or "additive".

-

5.19.2  Trait methods

-


+

5.19.2  Trait methods

+


diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 4e534c8b..3fbb24ba 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -146,13 +146,13 @@

The avatar parameter, if not "", sets a string value used to represent the species graphically, particularly in SLiMgui but perhaps in other contexts also.  The avatar should generally be a single character – usually an emoji corresponding to the species, such as "🦊" for foxes or "🐭" for mice.  If avatar is the empty string, "", SLiMgui will choose a default avatar.

The color parameter, if not "", sets a string color value used to represent the species in SLiMgui.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB" (see the Eidos manual for details).  If color is the empty string, "", SLiMgui will choose a default color.

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [logical$ directFitnessEffect = F])

-

Calling this function, added in SLiM 5.1, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

-

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not conflict with any other trait in any species in the model, it must not conflict with any global variable or constant, and it must not conflict with the name of any existing property on the Individual class.  The second requirement is because, after the new trait is created, a new global constant is defined that represents the trait’s index within the species, for quick reference to the trait in various contexts.  The third requirement is because, after the new trait is created, a new property is added to individuals of the species, with the same name as the new trait, that allows the trait values of individuals to be accessed directly as properties.  For example, if the new trait is named myTrait, a new global constant myTrait would be defined as myTrait’s index in the species, and access to an individual’s trait value would be possible through the property individual.myTrait.

+

Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

+

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not conflict with any other trait in any species in the model, it must not conflict with any global variable or constant, and it must not conflict with the name of any existing property on the Individual class.  The second requirement is because, after the new trait is created, a new global constant is defined that represents the trait’s index within the species, for quick reference to the trait in various contexts.  The third requirement is because, after the new trait is created, a new property is added to individuals of the species, with the same name as the new trait, that allows the trait values of individuals to be accessed directly as properties.  For example, if the new trait is named myTraitT, a new global constant myTraitT would be defined as myTraitT’s index in the species, and access to an individual’s trait value would be possible through the property individual.myTraitT.  It is suggested, but not required, that trait names should end with a capital T.

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive traits, such that the baseline offset has no effect upon the trait value.

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  As for the baseline offset, the individual offset mean defaults (if NULL is passed) to 1.0 for multiplicative traits, 0.0 for additive traits, to produce no effect.  The default standard deviation for the individual offset, if NULL is passed, is 0.0.  If NULL is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.

Finally, the directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

-

The use of the initializeTrait() function is optional.  If it is not called, a new Trait object will be created automatically, with a name generated from the species name plus a "T"; typically, then, the name is simT, except in multispecies models.  This default trait is configured to be multiplicative, with default values for the other parameters except directFitnessEffect, which is T for the default trait.  This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.1.  The creation of the default trait occurs as a side effect of the first call to initializeMutationType(), if initializeTrait() has not already been called.

+

The use of the initializeTrait() function is optional.  If it is not called, a new Trait object will be created automatically, with a name generated from the species name plus a "T"; typically, then, the name is simT, except in multispecies models.  This default trait is configured to be multiplicative, with default values for the other parameters except directFitnessEffect, which is T for the default trait.  This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.2.  The creation of the default trait occurs as a side effect of the first call to initializeMutationType(), if initializeTrait() has not already been called.

(void)initializeTreeSeq([logical$ recordMutations = T], [Nif$ simplificationRatio = NULL], [Ni$ simplificationInterval = NULL], [logical$ checkCoalescence = F], [logical$ runCrosschecks = F], [logical$ retainCoalescentOnly = T], [Ns$ timeUnit = NULL])

Configure options for tree sequence recording.  Calling this function turns on tree sequence recording, as a side effect, for later reconstruction of the simulation’s evolutionary dynamics; if you do not want tree sequence recording to be enabled, do not call this function.  Note that tree-sequence recording internally uses SLiM’s “pedigree tracking” feature to uniquely identify individuals and haplosomes; however, if you want to use pedigree tracking in your script you must still enable it yourself with initializeSLiMOptions(keepPedigrees=T).  A separate tree sequence will be recorded for each chromosome in the simulation, as configured with initializeChromosome().

The recordMutations flag controls whether information about individual mutations is recorded or not.  Such recording takes time and memory, and so can be turned off if only the tree sequence itself is needed, but it is turned on by default since mutation recording is generally useful.

diff --git a/SLiM.xcodeproj/project.pbxproj b/SLiM.xcodeproj/project.pbxproj index 582c2348..4da25ff4 100644 --- a/SLiM.xcodeproj/project.pbxproj +++ b/SLiM.xcodeproj/project.pbxproj @@ -640,6 +640,11 @@ 98ACDC9F253522B80038703F /* eidos_class_Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98ACDC9C253522B80038703F /* eidos_class_Object.cpp */; }; 98ACDCA0253522B80038703F /* eidos_class_Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98ACDC9C253522B80038703F /* eidos_class_Object.cpp */; }; 98ACDCA1253522B80038703F /* eidos_class_Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98ACDC9C253522B80038703F /* eidos_class_Object.cpp */; }; + 98B6741E2E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; + 98B6741F2E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; + 98B674202E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; + 98B674212E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; + 98B674222E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; 98C0943E1B7663DF00766A9A /* female_symbol.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98C0943C1B7663DF00766A9A /* female_symbol.pdf */; }; 98C0943F1B7663DF00766A9A /* male_symbol.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98C0943D1B7663DF00766A9A /* male_symbol.pdf */; }; 98C821241C7A980000548839 /* inline.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C820E51C7A980000548839 /* inline.c */; }; @@ -1600,11 +1605,6 @@ 98DB3D6F1E6122AE00E2C200 /* interaction_type.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */; }; 98DB3D701E6122AE00E2C200 /* interaction_type.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */; }; 98DB3D711E6122AE00E2C200 /* interaction_type.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */; }; - 98DC5A152E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; - 98DC5A162E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; - 98DC5A172E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; - 98DC5A182E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; - 98DC5A192E0C4A5900398F6B /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC5A132E0C4A5900398F6B /* trait.cpp */; }; 98DC9841289986B300160DD8 /* GitSHA1_Xcode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98DC983D289986B300160DD8 /* GitSHA1_Xcode.cpp */; }; 98DD5F022155B857009062EE /* change_folder.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98DD5F002155B857009062EE /* change_folder.pdf */; }; 98DD5F032155B857009062EE /* change_folder_H.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98DD5F012155B857009062EE /* change_folder_H.pdf */; }; @@ -2044,6 +2044,8 @@ 98ACDC9C253522B80038703F /* eidos_class_Object.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = eidos_class_Object.cpp; sourceTree = ""; }; 98ACDCA2253522D40038703F /* eidos_class_Object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = eidos_class_Object.h; sourceTree = ""; }; 98AF84641CB307300030D1EB /* VERSIONS */ = {isa = PBXFileReference; lastKnownFileType = text; path = VERSIONS; sourceTree = ""; }; + 98B6741D2E981AD400930737 /* trait.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = trait.cpp; sourceTree = ""; }; + 98B674232E981AFD00930737 /* trait.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = trait.h; sourceTree = ""; }; 98B8AF8625A2BE7200C95D66 /* json.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json.hpp; sourceTree = ""; }; 98B8AF8725A2BE7200C95D66 /* json_fwd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json_fwd.hpp; sourceTree = ""; }; 98C0943C1B7663DF00766A9A /* female_symbol.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = female_symbol.pdf; sourceTree = ""; }; @@ -2196,8 +2198,6 @@ 98D7ED2D28CE58FC00DEAAC4 /* slim_multi */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = slim_multi; sourceTree = BUILT_PRODUCTS_DIR; }; 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = interaction_type.cpp; sourceTree = ""; }; 98DB3D6E1E6122AE00E2C200 /* interaction_type.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = interaction_type.h; sourceTree = ""; }; - 98DC5A132E0C4A5900398F6B /* trait.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = trait.cpp; sourceTree = ""; }; - 98DC5A142E0C4A5900398F6B /* trait.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = trait.h; sourceTree = ""; }; 98DC9838289986B300160DD8 /* GitSHA1.cpp.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GitSHA1.cpp.in; sourceTree = ""; }; 98DC9839289986B300160DD8 /* GetGitRevisionDescription.cmake */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GetGitRevisionDescription.cmake; sourceTree = ""; }; 98DC983A289986B300160DD8 /* GetGitRevisionDescription.cmake.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GetGitRevisionDescription.cmake.in; sourceTree = ""; }; @@ -2521,8 +2521,8 @@ 9878A93E1A4E57E70007B9D6 /* species.h */, 9878A93D1A4E57E70007B9D6 /* species.cpp */, 98AC617924BA34ED0001914C /* species_eidos.cpp */, - 98DC5A142E0C4A5900398F6B /* trait.h */, - 98DC5A132E0C4A5900398F6B /* trait.cpp */, + 98B674232E981AFD00930737 /* trait.h */, + 98B6741D2E981AD400930737 /* trait.cpp */, 98E9A6981A3CD52A000AD4FC /* chromosome.h */, 98E9A6971A3CD52A000AD4FC /* chromosome.cpp */, 98E9A6AD1A3CD5D3000AD4FC /* population.h */, @@ -3876,6 +3876,7 @@ 98E9A6A81A3CD5A0000AD4FC /* haplosome.cpp in Sources */, 98CEFD202AAFABAA00D2C9B4 /* akima.c in Sources */, 981DC35028E26F8B000ABE91 /* eidos_functions_files.cpp in Sources */, + 98B6741E2E981AD400930737 /* trait.cpp in Sources */, 98CEFD882AAFB4F000D2C9B4 /* view.c in Sources */, 9807661C244934A800F6CBB4 /* zutil.c in Sources */, 98CEFD482AAFABAA00D2C9B4 /* interp2d.c in Sources */, @@ -3980,7 +3981,6 @@ 98C821271C7A980000548839 /* message.c in Sources */, 984824F1210B9F23002402A5 /* dtrsv.c in Sources */, 98C821421C7A980000548839 /* infnan.c in Sources */, - 98DC5A152E0C4A5900398F6B /* trait.cpp in Sources */, 9807662924493A8F00F6CBB4 /* crc32.c in Sources */, 98C8213F1C7A980000548839 /* zeta.c in Sources */, 9890D1ED27136BB7001EAE98 /* eidos_class_DataFrame.cpp in Sources */, @@ -4053,6 +4053,7 @@ 986152222B167AED0083E68F /* subpopulation.cpp in Sources */, 9861522F2B167B4E0083E68F /* linear.c in Sources */, 986151EE2B167A3A0083E68F /* eidos_functions_distributions.cpp in Sources */, + 98B674212E981AD400930737 /* trait.cpp in Sources */, 986152382B167B4E0083E68F /* tridiag.c in Sources */, 986152372B167B4E0083E68F /* weibull.c in Sources */, 986152752B167B4E0083E68F /* view.c in Sources */, @@ -4157,7 +4158,6 @@ 9861526D2B167B4E0083E68F /* mvgauss.c in Sources */, 986152622B167B4E0083E68F /* blas.c in Sources */, 986152392B167B4E0083E68F /* log.c in Sources */, - 98DC5A182E0C4A5900398F6B /* trait.cpp in Sources */, 9823568D252FE61A0096A745 /* eidos_class_Image.cpp in Sources */, 986151ED2B167A380083E68F /* eidos_functions_values.cpp in Sources */, 9861523F2B167B4E0083E68F /* inline.c in Sources */, @@ -4394,7 +4394,6 @@ 98CF522F294A3FC900557BBA /* eidos_functions_matrices.cpp in Sources */, 98CF5230294A3FC900557BBA /* gauss.c in Sources */, 98CF5231294A3FC900557BBA /* EidosHelpController.mm in Sources */, - 98DC5A172E0C4A5900398F6B /* trait.cpp in Sources */, 98CF5232294A3FC900557BBA /* community_eidos.cpp in Sources */, 98EDB4FA2E6538F200CC8798 /* permutation.c in Sources */, 98CF5233294A3FC900557BBA /* lodepng.cpp in Sources */, @@ -4454,6 +4453,7 @@ 98CF5263294A3FC900557BBA /* convert.c in Sources */, 98CEFD4A2AAFABAA00D2C9B4 /* interp2d.c in Sources */, 98CF5264294A3FC900557BBA /* gzlib.c in Sources */, + 98B674202E981AD400930737 /* trait.cpp in Sources */, 98CF5265294A3FC900557BBA /* erfc.c in Sources */, 98CF5266294A3FC900557BBA /* polymorphism.cpp in Sources */, 98EDB4E52E65366E00CC8798 /* daxpy.c in Sources */, @@ -4746,7 +4746,6 @@ 981DC35D28E26F8B000ABE91 /* eidos_functions_matrices.cpp in Sources */, 9876E5FD1ED559A600FF9762 /* gauss.c in Sources */, 982556651BA450980054CB3F /* EidosHelpController.mm in Sources */, - 98DC5A162E0C4A5900398F6B /* trait.cpp in Sources */, 9836868227CD72E900683639 /* community_eidos.cpp in Sources */, 98EDB4F92E6538F200CC8798 /* permutation.c in Sources */, 98235683252FDCF50096A745 /* lodepng.cpp in Sources */, @@ -4806,6 +4805,7 @@ 9854D2642278B9F8001D43BC /* convert.c in Sources */, 98CEFD492AAFABAA00D2C9B4 /* interp2d.c in Sources */, 9807661F244934A800F6CBB4 /* gzlib.c in Sources */, + 98B6741F2E981AD400930737 /* trait.cpp in Sources */, 9876E6091ED55B4F00FF9762 /* erfc.c in Sources */, 98D4C1DE1A6F543900FFB083 /* polymorphism.cpp in Sources */, 98EDB4E42E65366E00CC8798 /* daxpy.c in Sources */, @@ -5073,6 +5073,7 @@ 98D7ECC728CE58FC00DEAAC4 /* eidos_test_functions_math.cpp in Sources */, 98CEFD272AAFABAA00D2C9B4 /* akima.c in Sources */, 98D7ECC828CE58FC00DEAAC4 /* haplosome.cpp in Sources */, + 98B674222E981AD400930737 /* trait.cpp in Sources */, 98CEFD8F2AAFB4F000D2C9B4 /* view.c in Sources */, 98D7ECC928CE58FC00DEAAC4 /* zutil.c in Sources */, 98CEFD4F2AAFABAA00D2C9B4 /* interp2d.c in Sources */, @@ -5177,7 +5178,6 @@ 98D7ED1928CE58FC00DEAAC4 /* inline.c in Sources */, 98D7ED1A28CE58FC00DEAAC4 /* message.c in Sources */, 98D7ED1B28CE58FC00DEAAC4 /* dtrsv.c in Sources */, - 98DC5A192E0C4A5900398F6B /* trait.cpp in Sources */, 98D7ED1C28CE58FC00DEAAC4 /* infnan.c in Sources */, 98D7ED1D28CE58FC00DEAAC4 /* crc32.c in Sources */, 98D7ED1E28CE58FC00DEAAC4 /* zeta.c in Sources */, diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index dbdbb7ab..24a081df 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -6025,27 +6025,27 @@ nucleotide <\'96> (string$)\ \f5 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 selectionCoeff => (float$)\ +\f3\fs18 \cf0 selectionCoeff => (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The selection coefficient of the mutation, drawn from the distribution of fitness effects of its +\f4\fs20 \cf0 The selection coefficient of the mutation, drawn from the distribution of fitness effects of its \f3\fs18 MutationType -\f4\fs20 . If a mutation has a +\f5\fs20 . +\f4 \cf2 \expnd0\expndtw0\kerning0 + If a mutation has a \f3\fs18 selectionCoeff \f4\fs20 of \f1\i s -\f4\i0 and a -\f3\fs18 dominanceCoeff -\f4\fs20 of -\f1\i h \f4\i0 , the multiplicative fitness effect of the mutation in a homozygote is 1+ \f1\i s -\f4\i0 , and in a heterozygote is 1+ +\f4\i0 ; in a heterozygote it is 1+ \f1\i hs -\f4\i0 . The selection coefficient of a mutation can be changed with the -\f3\fs18 setSelectionCoeff() -\f4\fs20 method.\ -Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +\f4\i0 , where +\f1\i h +\f4\i0 is the dominance coefficient kept by the mutation type. +\f5 \cf0 \kerning1\expnd0\expndtw0 \ + +\f4 Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s selection coefficient to some number \f3\fs18 x @@ -6057,7 +6057,8 @@ Note that this property has a quirk: it is stored internally in SLiM using a sin \f3\fs18 id \f4\fs20 or \f3\fs18 tag -\f4\fs20 properties to identify particular mutations.\ +\f4\fs20 properties to identify particular mutations. +\f5 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 subpopID <\'96> (integer$)\ @@ -6110,8 +6111,7 @@ Changing this will normally affect the fitness values calculated toward the end \f4\fs20 \'96 but see the documentation of that method for caveats.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 \'96\'a0(void)setMutationType(io$\'a0mutType) -\f5 \ +\f3\fs18 \cf2 \'96\'a0(void)setMutationType(io$\'a0mutType)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Set the mutation type of the mutation to @@ -6127,12 +6127,10 @@ Changing this will normally affect the fitness values calculated toward the end \f4\fs20 methods of \f3\fs18 Mutation \f4\fs20 if so desired.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \expnd0\expndtw0\kerning0 In nucleotide-based models, a restriction applies: nucleotide-based mutations may not be changed to a non-nucleotide-based mutation type, and non-nucleotide-based mutations may not be changed to a nucleotide-based mutation type.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \kerning1\expnd0\expndtw0 \'96\'a0(void)setSelectionCoeff(float$\'a0selectionCoeff)\ +\f3\fs18 \cf2 \'96\'a0(void)setSelectionCoeff(float$\'a0selectionCoeff)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Set the selection coefficient of the mutation to @@ -6244,7 +6242,7 @@ SLiM consults this flag at the end of each tick when deciding whether to substit \f3\fs18 1.0 \f4\fs20 , and is used only in models where null haplosomes are present; the \f3\fs18 dominanceCoeff -\f4\fs20 property of the mutation is the dominance coefficient used in most circumstances. Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the +\f4\fs20 property is the dominance coefficient used in most circumstances. Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the \f3\fs18 Species \f4\fs20 method \f3\fs18 recalculateFitness() @@ -6371,7 +6369,7 @@ The species to which the target object belongs.\ \f1\i\fs22 \cf0 5.11.2 \f2\fs18 MutationType \f1\fs22 methods\ -\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\i0\fs18 \cf2 \'96\'a0(float$)defaultDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6397,7 +6395,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 , not the double-precision \f3\fs18 float \f4\fs20 type normally used by Eidos. This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.\ -\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(fs)distributionParamsForTrait([Nio\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6415,7 +6413,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 , and type \f3\fs18 float \f4\fs20 for all other DFE types.\ -\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(string$)distributionTypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6443,7 +6441,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 , as discussed in the \f3\fs18 MutationType \f4\fs20 class documentation.\ -\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(float)drawEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6461,7 +6459,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 class documentation for discussion of the supported distributions and their uses. If the distribution of effects is of type \f3\fs18 "s" \f4\fs20 , this method will result in synchronous execution of the script associated with the distribution of effects.\ -\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(void)setDefaultDominanceForTrait(Nio\'a0trait, float\'a0dominance)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6477,7 +6475,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of \f3\fs18 defaultDominance \f4\fs20 is used for each corresponding trait).\ -\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(void)setDistributionForTrait(Nio\'a0trait, string$\'a0distributionType, ...)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -13823,10 +13821,12 @@ nucleotide <\'96> (string$)\ \f5 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 selectionCoeff => (float$)\ +\f3\fs18 \cf0 selectionCoeff => (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The selection coefficient of the mutation, carried over from the original mutation object.\ +\f4\fs20 \cf0 The selection coefficient of the mutation, drawn from the distribution of fitness effects of its +\f3\fs18 MutationType +\f5\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 subpopID <\'96> (integer$)\ @@ -13867,10 +13867,10 @@ nucleotide <\'96> (string$)\ \f5\i0 \cf0 \ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 -\f0\b \cf0 5.19 Class Trait\ +\f0\b \cf2 5.19 Class Trait\ \pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 -\f1\i\b0 \cf0 5.19.1 +\f1\i\b0 \cf2 5.19.1 \f2\fs18 Trait \f1\fs22 properties\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -13959,10 +13959,10 @@ nucleotide <\'96> (string$)\ \f4\fs20 .\ \pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 -\f1\i\fs22 \cf0 5.19.2 +\f1\i\fs22 \cf2 5.19.2 \f2\fs18 Trait -\f1\fs22 methods\ -\pard\pardeftab397\fi274\ri720\sb40\sa40\partightenfactor0 - -\f5\i0 \cf0 \ +\f1\fs22 methods +\f4\i0 \ +\pard\pardeftab720\sa60\partightenfactor0 +\cf2 \ } \ No newline at end of file diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 002775bd..f2d4549f 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -931,8 +931,7 @@ This function is written in Eidos, and its source code can be viewed with \f2\fs20 , in which case the ellipsis should supply a \f1\fs18 string$ \f2\fs20 Eidos script parameter. The global symbol for the new mutation type is immediately available; the return value also provides the new object.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \expnd0\expndtw0\kerning0 +\expnd0\expndtw0\kerning0 Note that by default in WF models, all mutations of a given mutation type will be converted into \f1\fs18 Substitution \f2\fs20 objects when they reach fixation, for efficiency reasons. If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the @@ -1328,7 +1327,6 @@ If \f1\fs18 initializeChromosome() \f2\fs20 , allowing a different mutation run count to be specified for each chromosome in multi-chromosome models.\expnd0\expndtw0\kerning0 \ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 \kerning1\expnd0\expndtw0 If \f1\fs18 preventIncidentalSelfing \f2\fs20 is @@ -1408,7 +1406,6 @@ If \f2\fs20 for \f1\fs18 checkInfiniteLoops \f2\fs20 to disable these checks. There is no way to turn these checks on or off for individual loops; it is a global setting.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 This function will likely be extended with further options in the future, added on to the end of the argument list. Using named arguments with this call is recommended for readability. Note that turning on optional features may increase the runtime and memory footprint of SLiM.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -1476,7 +1473,7 @@ The \f1\fs18 \cf2 (object$)initializeTrait(string$\'a0name, string$\'a0type, [Nf$\'a0baselineOffset\'a0=\'a0NULL], [Nf$\'a0individualOffsetMean\'a0=\'a0NULL], [Nf$\'a0individualOffsetSD\'a0=\'a0NULL], [logical$\'a0directFitnessEffect\'a0=\'a0F])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f2\fs20 \cf2 Calling this function, added in SLiM 5.1, configures a phenotypic trait in the species being initialized. The new +\f2\fs20 \cf2 Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized. The new \f1\fs18 Trait \f2\fs20 object is returned. For more details on the way that traits work in SLiM, beyond what is given below, see the \f1\fs18 Trait @@ -1486,13 +1483,15 @@ The \f2\fs20 parameter gives the name of the new trait. This may be any (non-empty) string, except that it must not conflict with any other trait in any species in the model, it must not conflict with any global variable or constant, and it must not conflict with the name of any existing property on the \f1\fs18 Individual \f2\fs20 class. The second requirement is because, after the new trait is created, a new global constant is defined that represents the trait\'92s index within the species, for quick reference to the trait in various contexts. The third requirement is because, after the new trait is created, a new property is added to individuals of the species, with the same name as the new trait, that allows the trait values of individuals to be accessed directly as properties. For example, if the new trait is named -\f1\fs18 myTrait +\f1\fs18 myTraitT \f2\fs20 , a new global constant -\f1\fs18 myTrait +\f1\fs18 myTraitT \f2\fs20 would be defined as -\f1\fs18 myTrait +\f1\fs18 myTraitT \f2\fs20 \'92s index in the species, and access to an individual\'92s trait value would be possible through the property -\f1\fs18 individual.myTrait +\f1\fs18 individual.myTraitT +\f2\fs20 . It is suggested, but not required, that trait names should end with a capital +\f1\fs18 T \f2\fs20 .\ The \f1\fs18 type @@ -1554,7 +1553,7 @@ The use of the \f1\fs18 directFitnessEffect \f2\fs20 , which is \f1\fs18 T -\f2\fs20 for the default trait. This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.1. The creation of the default trait occurs as a side effect of the first call to +\f2\fs20 for the default trait. This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.2. The creation of the default trait occurs as a side effect of the first call to \f1\fs18 initializeMutationType() \f2\fs20 , if \f1\fs18 initializeTrait() diff --git a/VERSIONS b/VERSIONS index 810f7154..6e2ff173 100644 --- a/VERSIONS +++ b/VERSIONS @@ -15,6 +15,37 @@ development head (in the master branch): extend text() to support drawing text and an angle, with new [float angle = 0.0] parameter add mtext() call to Plot, for drawing text in the margins outside the plot area add rowSums() and colSums() functions to Eidos, for use with matrices as a faster alternative to apply() + ----- last merge into multitrait was done here + + +multitrait branch: + add new Eidos SLiM class, Trait + add (object$)initializeTrait(s$ name, s$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [l$ directFitnessEffect = F]) + add Species property traits => (object) to simply get all traits defined for a species + add Species methods – (object)traitsWithIndices(integer indices) and – (object)traitsWithNames(string names) + add Trait properties: + baselineOffset <-> (float$) + directFitnessEffect <-> (logical$) + index => (integer$) + individualOffsetMean <-> (float$) + individualOffsetSD <-> (float$) + name => (string$) + species => (object$) + tag <-> (integer$) + type => (string$) + add Community property allTraits => (object) + add a C++ dominance_coeff_ property to Mutation, with a value inherited from MutationType's property (which is now just the default value) + add dominanceCoeff properties to Mutation and Substitution + add a setDominanceCoeff() method to Mutation, yay! + fix calcInbreedingLoad() to use muts.dominanceCoeff instead of muts.mutationType.dominanceCoeff + revamp MutationType for multiple traits + remove MutationType properties dominanceCoeff, distributionType, and distributionParams properties + add MutationType methods defaultDominanceForTrait(), distributionTypeForTrait(), and distributionParamsForTrait() + change MutationType method setDistribution() to setDistributionForTrait() + change MutationType method drawSelectionCoefficient() to drawEffectForTrait() + add SLiMgui autofixing for all of the above changes + add MutationType method setDefaultDominanceForTrait() (approximately replacing writing into the dominanceCoeff property, but this should not autofix) + transition MutationType's internals to keep a separate DE for each trait version 5.1 (Eidos version 4.1): @@ -63,36 +94,6 @@ version 5.1 (Eidos version 4.1): extend subsetMutations() to support subsetting mutations belonging to more than one chromosome fix #560, add recipe 14.16 "Visualizing linkage disequilibrium" to demonstrate the new calcLD_D() and calcLD_Rsquared() functions -multitrait branch: - add new Eidos SLiM class, Trait - add (object$)initializeTrait(s$ name, s$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [l$ directFitnessEffect = F]) - add Species property traits => (object) to simply get all traits defined for a species - add Species methods – (object)traitsWithIndices(integer indices) and – (object)traitsWithNames(string names) - add Trait properties: - baselineOffset <-> (float$) - directFitnessEffect <-> (logical$) - index => (integer$) - individualOffsetMean <-> (float$) - individualOffsetSD <-> (float$) - name => (string$) - species => (object$) - tag <-> (integer$) - type => (string$) - add Community property allTraits => (object) - add a C++ dominance_coeff_ property to Mutation, with a value inherited from MutationType's property (which is now just the default value) - add dominanceCoeff properties to Mutation and Substitution - add a setDominanceCoeff() method to Mutation, yay! - fix calcInbreedingLoad() to use muts.dominanceCoeff instead of muts.mutationType.dominanceCoeff - revamp MutationType for multiple traits - remove MutationType properties dominanceCoeff, distributionType, and distributionParams properties - add MutationType methods defaultDominanceForTrait(), distributionTypeForTrait(), and distributionParamsForTrait() - change MutationType method setDistribution() to setDistributionForTrait() - change MutationType method drawSelectionCoefficient() to drawEffectForTrait() - add SLiMgui autofixing for all of the above changes - add MutationType method setDefaultDominanceForTrait() (approximately replacing writing into the dominanceCoeff property, but this should not autofix) - transition MutationType's internals to keep a separate DE for each trait - - version 5.0 (Eidos version 4.0): multi-chromosome transition: --- main changes to existing APIs: From 7a82d7a61451eed70f24f24fa6ba7969098e3db5 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 10 Oct 2025 14:19:17 -0400 Subject: [PATCH 005/107] some polish for the multitrait work already done --- QtSLiM/QtSLiMWindow.cpp | 6 +- QtSLiM/help/SLiMHelpClasses.html | 10 +-- SLiMgui/SLiMHelpClasses.rtf | 42 ++++----- VERSIONS | 20 +++-- core/individual.cpp | 4 +- core/mutation_type.cpp | 40 ++++----- core/mutation_type.h | 6 +- core/slim_globals.cpp | 6 +- core/slim_globals.h | 12 +-- core/slim_test_genetics.cpp | 142 +++++++++++++++---------------- 10 files changed, 147 insertions(+), 141 deletions(-) diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index 4f9a83a7..bebb809a 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -2139,16 +2139,16 @@ bool QtSLiMWindow::checkTerminationForAutofix(QString terminationMessage) if (terminationMessage.contains("property distributionType is not defined for object element type MutationType") && (selectionString == "distributionType")) - return offerAndExecuteAutofix(selection, "distributionTypeForTrait()", "The `distributionType` property of MutationType has become the method `distributionTypeForTrait()`.", terminationMessage); + return offerAndExecuteAutofix(selection, "effectDistributionTypeForTrait()", "The `distributionType` property of MutationType has become the method `effectDistributionTypeForTrait()`.", terminationMessage); if (terminationMessage.contains("property distributionParams is not defined for object element type MutationType") && (selectionString == "distributionParams")) - return offerAndExecuteAutofix(selection, "distributionParamsForTrait()", "The `distributionParams` property of MutationType has become the method `distributionParamsForTrait()`.", terminationMessage); + return offerAndExecuteAutofix(selection, "effectDistributionParamsForTrait()", "The `distributionParams` property of MutationType has become the method `effectDistributionParamsForTrait()`.", terminationMessage); if ((afterSelection1String == "(") && terminationMessage.contains("method setDistribution() is not defined on object element type MutationType") && (selectionPlus1AfterString == "setDistribution(")) - return offerAndExecuteAutofix(selectionPlus1After, "setDistributionForTrait(NULL, ", "The `setDistribution()` method of MutationType has become the method `setDistributionForTrait()`.", terminationMessage); + return offerAndExecuteAutofix(selectionPlus1After, "setEffectDistributionForTrait(NULL, ", "The `setDistribution()` method of MutationType has become the method `setEffectDistributionForTrait()`.", terminationMessage); if (terminationMessage.contains("method drawSelectionCoefficient() is not defined on object element type MutationType") && (selectionString == "drawSelectionCoefficient")) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 0c93ad57..f528ea45 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -737,15 +737,15 @@

Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their dominanceCoeff property, but that can be changed later with the Mutation method setDominanceCoeff().

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

-

– (fs)distributionParamsForTrait([Nio<Trait> trait = NULL])

-

Returns the parameters that configure the distribution of effects for the specified trait or traits.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DFE type "s", and type float for all other DFE types.

-

– (string$)distributionTypeForTrait([Nio<Trait> trait = NULL])

-

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

– (float)drawEffectForTrait([Nio<Trait> trait = NULL], [integer$ n = 1])

Draws and returns a vector of n mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  See the MutationType class documentation for discussion of the supported distributions and their uses.  If the distribution of effects is of type "s", this method will result in synchronous execution of the script associated with the distribution of effects.

+

– (fs)effectDistributionParamsForTrait([Nio<Trait> trait = NULL])

+

Returns the parameters that configure the distribution of effects for the specified trait or traits.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DFE type "s", and type float for all other DFE types.

+

– (string$)effectDistributionTypeForTrait([Nio<Trait> trait = NULL])

+

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

– (void)setDefaultDominanceForTrait(Nio<Trait> trait, float dominance)

Set the default dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of defaultDominance must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of defaultDominance is used for each corresponding trait).

-

– (void)setDistributionForTrait(Nio<Trait> trait, string$ distributionType, ...)

+

– (void)setEffectDistributionForTrait(Nio<Trait> trait, string$ distributionType, ...)

Set the distribution of effects for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.

The distributionType may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for the exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  See the MutationType class documentation for discussion of these distributions and their uses.  The distribution of effects for a mutation type is normally a constant in simulations, so be sure you know what you are doing.

5.12  Class Plot

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 24a081df..a7d8ea5a 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -6397,7 +6397,25 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 type normally used by Eidos. This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(fs)distributionParamsForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)drawEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Draws and returns a vector of +\f3\fs18 n +\f4\fs20 mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. See the +\f3\fs18 MutationType +\f4\fs20 class documentation for discussion of the supported distributions and their uses. If the distribution of effects is of type +\f3\fs18 "s" +\f4\fs20 , this method will result in synchronous execution of the script associated with the distribution of effects.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(fs)effectDistributionParamsForTrait([Nio\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the parameters that configure the distribution of effects for the specified trait or traits. The traits can be specified as @@ -6415,7 +6433,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 for all other DFE types.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(string$)distributionTypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(string$)effectDistributionTypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the type of distribution of effects for the specified trait or traits. The traits can be specified as @@ -6443,24 +6461,6 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 class documentation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)drawEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf2 Draws and returns a vector of -\f3\fs18 n -\f4\fs20 mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type. The traits can be specified as -\f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as -\f3\fs18 Trait -\f4\fs20 objects; -\f3\fs18 NULL -\f4\fs20 represents all of the traits in the species. See the -\f3\fs18 MutationType -\f4\fs20 class documentation for discussion of the supported distributions and their uses. If the distribution of effects is of type -\f3\fs18 "s" -\f4\fs20 , this method will result in synchronous execution of the script associated with the distribution of effects.\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - \f3\fs18 \cf2 \'96\'a0(void)setDefaultDominanceForTrait(Nio\'a0trait, float\'a0dominance)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6477,7 +6477,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 is used for each corresponding trait).\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(void)setDistributionForTrait(Nio\'a0trait, string$\'a0distributionType, ...)\ +\f3\fs18 \cf2 \'96\'a0(void)setEffectDistributionForTrait(Nio\'a0trait, string$\'a0distributionType, ...)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Set the distribution of effects for a specified trait or traits, for the target mutation type. The traits can be specified as diff --git a/VERSIONS b/VERSIONS index 6e2ff173..173d2169 100644 --- a/VERSIONS +++ b/VERSIONS @@ -12,13 +12,15 @@ development head (in the master branch): fix display of images in Plot; it was antialiasing in some cases (such as PDF generation), which is not desirable add segments() call to Plot, for plotting a set of unconnected line segments add rects() call to Plot, for plotting a set of rectangles - extend text() to support drawing text and an angle, with new [float angle = 0.0] parameter + extend text() to support drawing text at an angle, with new [float angle = 0.0] parameter add mtext() call to Plot, for drawing text in the margins outside the plot area add rowSums() and colSums() functions to Eidos, for use with matrices as a faster alternative to apply() ----- last merge into multitrait was done here multitrait branch: + constant SLIM_MAX_TRAITS defines a max of 256 traits per species, but so far there is no actual need for this maximum + internal C++ TraitType enum defines two types of traits, kMultiplicative and kAdditive add new Eidos SLiM class, Trait add (object$)initializeTrait(s$ name, s$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [l$ directFitnessEffect = F]) add Species property traits => (object) to simply get all traits defined for a species @@ -34,18 +36,22 @@ multitrait branch: tag <-> (integer$) type => (string$) add Community property allTraits => (object) + make a single implicit trait with MakeImplicitTrait() (defaulting to kMultiplicative with a direct fitness effect) if initializeTrait() is not called before initializeMutationType() is called add a C++ dominance_coeff_ property to Mutation, with a value inherited from MutationType's property (which is now just the default value) add dominanceCoeff properties to Mutation and Substitution add a setDominanceCoeff() method to Mutation, yay! fix calcInbreedingLoad() to use muts.dominanceCoeff instead of muts.mutationType.dominanceCoeff revamp MutationType for multiple traits remove MutationType properties dominanceCoeff, distributionType, and distributionParams properties - add MutationType methods defaultDominanceForTrait(), distributionTypeForTrait(), and distributionParamsForTrait() - change MutationType method setDistribution() to setDistributionForTrait() - change MutationType method drawSelectionCoefficient() to drawEffectForTrait() - add SLiMgui autofixing for all of the above changes - add MutationType method setDefaultDominanceForTrait() (approximately replacing writing into the dominanceCoeff property, but this should not autofix) - transition MutationType's internals to keep a separate DE for each trait + add MutationType methods defaultDominanceForTrait([Nio trait = NULL]), effectDistributionTypeForTrait([Nio trait = NULL]), and effectDistributionParamsForTrait([Nio trait = NULL]) + change MutationType method setDistribution() to setEffectDistributionForTrait(Nio trait, string$ distributionType, ...) + change MutationType method drawSelectionCoefficient() to drawEffectForTrait([Nio trait = NULL], [integer$ n = 1]) + add SLiMgui autofixing for all of the above changes in QtSLiMWindow::checkTerminationForAutofix() + add MutationType method setDefaultDominanceForTrait(Nio trait, float dominance) (approximately replacing writing into the dominanceCoeff property, but this should not autofix) + transition MutationType's internals to keep a separate DE for each trait using a new EffectDistributionInfo struct + added C++ IsPureNeutralDFE() method to represent whether all of the effects of a given mutation type are all neutral + add Individual properties for each trait in the individual's species, allowing direct access + this was done by adding GetProperty_NO_SIGNATURE() / SetProperty_NO_SIGNATURE() methods called by EidosValue_Object::GetPropertyOfElements() and EidosValue_Object::SetPropertyOfElements() to support properties with no signature version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index 6a082560..05eb6396 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -5187,7 +5187,7 @@ EidosValue_SP Individual_Class::GetProperty_NO_SIGNATURE(EidosGlobalStringID p_p if (trait) { // We got a hit, but don't know what to do with it for now - EIDOS_TERMINATION << "ERROR (Individual_Class::GetProperty_NO_SIGNATURE): trait " << trait->Name() << " cannot be accessed (FIXME)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::GetProperty_NO_SIGNATURE): trait " << trait->Name() << " cannot be accessed (FIXME MULTITRAIT)." << EidosTerminate(); } return super::GetProperty_NO_SIGNATURE(p_property_id, p_targets, p_targets_size); @@ -5207,7 +5207,7 @@ void Individual_Class::SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_i EIDOS_TERMINATION << "ERROR (Individual_Class::SetProperty_NO_SIGNATURE): assigned value must be of type float for trait-value property " << trait->Name() << "." << EidosTerminate(); // We got a hit, but don't know what to do with it for now - EIDOS_TERMINATION << "ERROR (Individual_Class::GetProperty_NO_SIGNATURE): trait " << trait->Name() << " cannot be accessed (FIXME)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::GetProperty_NO_SIGNATURE): trait " << trait->Name() << " cannot be accessed (FIXME MULTITRAIT)." << EidosTerminate(); } return super::SetProperty_NO_SIGNATURE(p_property_id, p_targets, p_targets_size, p_value); diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 2ed063b1..8aeafd8c 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -692,13 +692,13 @@ EidosValue_SP MutationType::ExecuteInstanceMethod(EidosGlobalStringID p_method_i { switch (p_method_id) { - case gID_defaultDominanceForTrait: return ExecuteMethod_defaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); - case gID_distributionTypeForTrait: return ExecuteMethod_distributionTypeForTrait(p_method_id, p_arguments, p_interpreter); - case gID_distributionParamsForTrait: return ExecuteMethod_distributionParamsForTrait(p_method_id, p_arguments, p_interpreter); - case gID_drawEffectForTrait: return ExecuteMethod_drawEffectForTrait(p_method_id, p_arguments, p_interpreter); - case gID_setDefaultDominanceForTrait: return ExecuteMethod_setDefaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); - case gID_setDistributionForTrait: return ExecuteMethod_setDistributionForTrait(p_method_id, p_arguments, p_interpreter); - default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); + case gID_defaultDominanceForTrait: return ExecuteMethod_defaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_effectDistributionTypeForTrait: return ExecuteMethod_effectDistributionTypeForTrait(p_method_id, p_arguments, p_interpreter); + case gID_effectDistributionParamsForTrait: return ExecuteMethod_effectDistributionParamsForTrait(p_method_id, p_arguments, p_interpreter); + case gID_drawEffectForTrait: return ExecuteMethod_drawEffectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_setDefaultDominanceForTrait: return ExecuteMethod_setDefaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_setEffectDistributionForTrait: return ExecuteMethod_setEffectDistributionForTrait(p_method_id, p_arguments, p_interpreter); + default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } } @@ -735,16 +735,16 @@ EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalSt } } -// ********************* - (fs)distributionParamsForTrait([Nio trait = NULL]) +// ********************* - (fs)effectDistributionParamsForTrait([Nio trait = NULL]) // -EidosValue_SP MutationType::ExecuteMethod_distributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); // get the trait indices, with bounds-checking std::vector trait_indices; - species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultDominanceForTrait"); + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectDistributionParamsForTrait"); // decide whether doing floats or strings; must be the same for all bool is_float = false; @@ -761,7 +761,7 @@ EidosValue_SP MutationType::ExecuteMethod_distributionParamsForTrait(EidosGlobal } if (is_float && is_string) - EIDOS_TERMINATION << "ERROR (ExecuteMethod_drawEffectForTrait): drawEffectForTrait() requires all specified traits to have either float or string parameters (not a mixture) for their distributions of effects." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (ExecuteMethod_effectDistributionParamsForTrait): effectDistributionParamsForTrait() requires all specified traits to have either float or string parameters (not a mixture) for their distributions of effects." << EidosTerminate(nullptr); if (is_float) { @@ -793,16 +793,16 @@ EidosValue_SP MutationType::ExecuteMethod_distributionParamsForTrait(EidosGlobal } } -// ********************* - (string$)distributionTypeForTrait([Nio trait = NULL]) +// ********************* - (string$)effectDistributionTypeForTrait([Nio trait = NULL]) // -EidosValue_SP MutationType::ExecuteMethod_distributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_effectDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); // get the trait indices, with bounds-checking std::vector trait_indices; - species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultDominanceForTrait"); + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectDistributionTypeForTrait"); // assemble the result EidosValue_String *string_result = new (gEidosValuePool->AllocateChunk()) EidosValue_String(); @@ -838,7 +838,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID // get the trait indices, with bounds-checking std::vector trait_indices; - species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultDominanceForTrait"); + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "drawEffectForTrait"); // get the number of effects to draw int64_t num_draws = n_value->IntAtIndex_NOCAST(0, nullptr); @@ -914,9 +914,9 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba return gStaticEidosValueVOID; } -// ********************* - (void)setDistributionForTrait(Nio trait, string$ distributionType, ...) +// ********************* - (void)setEffectDistributionForTrait(Nio trait, string$ distributionType, ...) // -EidosValue_SP MutationType::ExecuteMethod_setDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); @@ -1010,11 +1010,11 @@ const std::vector *MutationType_Class::Methods(void) c methods = new std::vector(*super::Methods()); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultDominanceForTrait, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_distributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_distributionTypeForTrait, kEidosValueMaskString | kEidosValueMaskSingleton))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString | kEidosValueMaskSingleton))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDistributionForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectDistributionForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/mutation_type.h b/core/mutation_type.h index 563d8bee..a0fc509c 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -197,11 +197,11 @@ class MutationType : public EidosDictionaryUnretained virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; EidosValue_SP ExecuteMethod_defaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_distributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_distributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_effectDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_setDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index c100f1ed..a15cb71c 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1254,8 +1254,8 @@ const std::string &gStr_selectionCoeff = EidosRegisteredString("selectionCoeff", const std::string &gStr_subpopID = EidosRegisteredString("subpopID", gID_subpopID); const std::string &gStr_convertToSubstitution = EidosRegisteredString("convertToSubstitution", gID_convertToSubstitution); const std::string &gStr_defaultDominanceForTrait = EidosRegisteredString("defaultDominanceForTrait", gID_defaultDominanceForTrait); -const std::string &gStr_distributionTypeForTrait = EidosRegisteredString("distributionTypeForTrait", gID_distributionTypeForTrait); -const std::string &gStr_distributionParamsForTrait = EidosRegisteredString("distributionParamsForTrait", gID_distributionParamsForTrait); +const std::string &gStr_effectDistributionTypeForTrait = EidosRegisteredString("effectDistributionTypeForTrait", gID_effectDistributionTypeForTrait); +const std::string &gStr_effectDistributionParamsForTrait = EidosRegisteredString("effectDistributionParamsForTrait", gID_effectDistributionParamsForTrait); const std::string &gStr_dominanceCoeff = EidosRegisteredString("dominanceCoeff", gID_dominanceCoeff); const std::string &gStr_hemizygousDominanceCoeff = EidosRegisteredString("hemizygousDominanceCoeff", gID_hemizygousDominanceCoeff); const std::string &gStr_mutationStackGroup = EidosRegisteredString("mutationStackGroup", gID_mutationStackGroup); @@ -1381,7 +1381,7 @@ const std::string &gStr_setDominanceCoeff = EidosRegisteredString("setDominanceC const std::string &gStr_setMutationType = EidosRegisteredString("setMutationType", gID_setMutationType); const std::string &gStr_drawEffectForTrait = EidosRegisteredString("drawEffectForTrait", gID_drawEffectForTrait); const std::string &gStr_setDefaultDominanceForTrait = EidosRegisteredString("setDefaultDominanceForTrait", gID_setDefaultDominanceForTrait); -const std::string &gStr_setDistributionForTrait = EidosRegisteredString("setDistributionForTrait", gID_setDistributionForTrait); +const std::string &gStr_setEffectDistributionForTrait = EidosRegisteredString("setEffectDistributionForTrait", gID_setEffectDistributionForTrait); const std::string &gStr_addPatternForClone = EidosRegisteredString("addPatternForClone", gID_addPatternForClone); const std::string &gStr_addPatternForCross = EidosRegisteredString("addPatternForCross", gID_addPatternForCross); const std::string &gStr_addPatternForNull = EidosRegisteredString("addPatternForNull", gID_addPatternForNull); diff --git a/core/slim_globals.h b/core/slim_globals.h index 0ab06809..d128e0ec 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -843,8 +843,8 @@ extern const std::string &gStr_selectionCoeff; extern const std::string &gStr_subpopID; extern const std::string &gStr_convertToSubstitution; extern const std::string &gStr_defaultDominanceForTrait; -extern const std::string &gStr_distributionTypeForTrait; -extern const std::string &gStr_distributionParamsForTrait; +extern const std::string &gStr_effectDistributionTypeForTrait; +extern const std::string &gStr_effectDistributionParamsForTrait; extern const std::string &gStr_dominanceCoeff; extern const std::string &gStr_hemizygousDominanceCoeff; extern const std::string &gStr_mutationStackGroup; @@ -969,7 +969,7 @@ extern const std::string &gStr_setDominanceCoeff; extern const std::string &gStr_setMutationType; extern const std::string &gStr_drawEffectForTrait; extern const std::string &gStr_setDefaultDominanceForTrait; -extern const std::string &gStr_setDistributionForTrait; +extern const std::string &gStr_setEffectDistributionForTrait; extern const std::string &gStr_addPatternForClone; extern const std::string &gStr_addPatternForCross; extern const std::string &gStr_addPatternForNull; @@ -1318,8 +1318,8 @@ enum _SLiMGlobalStringID : int { gID_subpopID, gID_convertToSubstitution, gID_defaultDominanceForTrait, - gID_distributionTypeForTrait, - gID_distributionParamsForTrait, + gID_effectDistributionTypeForTrait, + gID_effectDistributionParamsForTrait, gID_dominanceCoeff, gID_hemizygousDominanceCoeff, gID_mutationStackGroup, @@ -1444,7 +1444,7 @@ enum _SLiMGlobalStringID : int { gID_setMutationType, gID_drawEffectForTrait, gID_setDefaultDominanceForTrait, - gID_setDistributionForTrait, + gID_setEffectDistributionForTrait, gID_addPatternForClone, gID_addPatternForCross, gID_addPatternForNull, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index a471e16a..35a6fc50 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -39,8 +39,8 @@ void _RunMutationTypeTests(void) SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.convertToSubstitution == T) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.mutationStackGroup == 1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.mutationStackPolicy == 's') stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.distributionParamsForTrait() == 0.0) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.distributionTypeForTrait() == 'f') stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.effectDistributionParamsForTrait() == 0.0) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.effectDistributionTypeForTrait() == 'f') stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.defaultDominanceForTrait() == 0.5) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.id == 1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.color = ''; } 2 early() { if (m1.color == '') stop(); }", __LINE__); @@ -70,78 +70,78 @@ void _RunMutationTypeTests(void) SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultDominanceForTrait(NULL, 0.3); }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultDominanceForTrait(0, 0.3); }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultDominanceForTrait(sim.traits, 0.3); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', 2.2); if (m1.distributionTypeForTrait() == 'f' & m1.distributionParamsForTrait() == 2.2) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 7.5); if (m1.distributionTypeForTrait() == 'g' & identical(m1.distributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', -3); if (m1.distributionTypeForTrait() == 'e' & m1.distributionParamsForTrait() == -3) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, 7.5); if (m1.distributionTypeForTrait() == 'n' & identical(m1.distributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 7.5); if (m1.distributionTypeForTrait() == 'p' & identical(m1.distributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 7.5); if (m1.distributionTypeForTrait() == 'w' & identical(m1.distributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 'return 1;'); if (m1.distributionTypeForTrait() == 's' & identical(m1.distributionParamsForTrait(), 'return 1;')) stop(); }", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'x', 1.5); stop(); }", "must be 'f', 'g', 'e', 'n', 'w', or 's'", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 3); stop(); }", "must be of type string", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 3.1); stop(); }", "must be of type string", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', T); stop(); }", "must be of type string", __LINE__); - - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, -1.0); }", "must have a shape parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, -1.0); }", "must have a standard deviation parameter >= 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 0.0); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, -1.0); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 0.0, 7.5); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', -1.0, 7.5); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, -7.5); }", "must have a shape parameter > 0", __LINE__); - - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setDistributionForTrait(NULL, 's', 'return foo;'); } 100 early() { stop(); }", "undefined identifier foo", __LINE__, false); - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setDistributionForTrait(NULL, 's', 'x >< 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DFE callback script", __LINE__, false); - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setDistributionForTrait(NULL, 's', 'x $ 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DFE callback script", __LINE__, false); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (m1.effectDistributionTypeForTrait() == 'f' & m1.effectDistributionParamsForTrait() == 2.2) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'g' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3); if (m1.effectDistributionTypeForTrait() == 'e' & m1.effectDistributionParamsForTrait() == -3) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'n' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'p' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'w' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'return 1;'); if (m1.effectDistributionTypeForTrait() == 's' & identical(m1.effectDistributionParamsForTrait(), 'return 1;')) stop(); }", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'x', 1.5); stop(); }", "must be 'f', 'g', 'e', 'n', 'w', or 's'", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 3); stop(); }", "must be of type string", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 3.1); stop(); }", "must be of type string", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', T); stop(); }", "must be of type string", __LINE__); + + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, -1.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, -1.0); }", "must have a standard deviation parameter >= 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 0.0); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, -1.0); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 0.0, 7.5); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', -1.0, 7.5); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, -7.5); }", "must have a shape parameter > 0", __LINE__); + + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'return foo;'); } 100 early() { stop(); }", "undefined identifier foo", __LINE__, false); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'x >< 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DFE callback script", __LINE__, false); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'x $ 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DFE callback script", __LINE__, false); // Test MutationType - (float)drawSelectionCoefficient([integer$ n = 1]) // the parameters here are chosen so that these tests should fail extremely rarely - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', 2.2); if (m1.drawEffectForTrait() == 2.2) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'f', 2.2); if (identical(m1.drawEffectForTrait(NULL, 10), rep(2.2, 10))) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'g', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 3.1) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', -3.0); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'e', -3.0); if (abs(mean(m1.drawEffectForTrait(NULL, 30000)) + 3.0) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, 0.5); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'n', 3.1, 0.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'p', 3.1, 0.01); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 'w', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 2.910106) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 2.0) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (m1.drawEffectForTrait() == 2.2) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (identical(m1.drawEffectForTrait(NULL, 10), rep(2.2, 10))) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3.0); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3.0); if (abs(mean(m1.drawEffectForTrait(NULL, 30000)) + 3.0) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 0.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 0.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 0.01); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 2.910106) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 2.0) < 0.1) stop(); }", __LINE__); } #pragma mark GenomicElementType tests From db483d0c5450885f0496d9f1f26a5c12657d07fa Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 10 Oct 2025 14:26:08 -0400 Subject: [PATCH 006/107] fix non-breaking spaces that crept in --- VERSIONS | 10 +++++----- core/mutation_type.cpp | 12 ++++++------ core/slim_test_other.cpp | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/VERSIONS b/VERSIONS index 173d2169..20d0e163 100644 --- a/VERSIONS +++ b/VERSIONS @@ -43,11 +43,11 @@ multitrait branch: fix calcInbreedingLoad() to use muts.dominanceCoeff instead of muts.mutationType.dominanceCoeff revamp MutationType for multiple traits remove MutationType properties dominanceCoeff, distributionType, and distributionParams properties - add MutationType methods defaultDominanceForTrait([Nio trait = NULL]), effectDistributionTypeForTrait([Nio trait = NULL]), and effectDistributionParamsForTrait([Nio trait = NULL]) - change MutationType method setDistribution() to setEffectDistributionForTrait(Nio trait, string$ distributionType, ...) - change MutationType method drawSelectionCoefficient() to drawEffectForTrait([Nio trait = NULL], [integer$ n = 1]) + add MutationType methods defaultDominanceForTrait([Nio trait = NULL]), effectDistributionTypeForTrait([Nio trait = NULL]), and effectDistributionParamsForTrait([Nio trait = NULL]) + change MutationType method setDistribution() to setEffectDistributionForTrait(Nio trait, string$ distributionType, ...) + change MutationType method drawSelectionCoefficient() to drawEffectForTrait([Nio trait = NULL], [integer$ n = 1]) add SLiMgui autofixing for all of the above changes in QtSLiMWindow::checkTerminationForAutofix() - add MutationType method setDefaultDominanceForTrait(Nio trait, float dominance) (approximately replacing writing into the dominanceCoeff property, but this should not autofix) + add MutationType method setDefaultDominanceForTrait(Nio trait, float dominance) (approximately replacing writing into the dominanceCoeff property, but this should not autofix) transition MutationType's internals to keep a separate DE for each trait using a new EffectDistributionInfo struct added C++ IsPureNeutralDFE() method to represent whether all of the effects of a given mutation type are all neutral add Individual properties for each trait in the individual's species, allowing direct access @@ -162,7 +162,7 @@ version 5.0 (Eidos version 4.0): policy change: the 1D SFS graph and haplotype plot no longer depend upon the current chromosome range selection; that was weird, and doesn't work well in multichrom policy change: the "remove fixed mutations" stage of the WF tick cycle is now after "offspring become parents" (no user-visible difference except WF tick cycle diagram) policy change: mutationRuns= for initializeSLiMOptions() has been changed to [l$ doMutationRunExperiments=T]; pass mutationRuns= to initializeChromosome() instead - policy change (slight): the haplosome1Null and haplosome2Null parameters to addEmpty() now apply only to type "A" chromosomes, causing a minor break in backward compatibility + policy change (slight): the haplosome1Null and haplosome2Null parameters to addEmpty() now apply only to type "A" chromosomes, causing a minor break in backward compatibility policy change: randomizeStrands must now usually be explicitly specified for both addRecombinant() and addMultiRecombinant(); the default is now NULL, and NULL errors so that T or F must be explicitly given unless it does not matter (i.e., there is no recombination) policy change: fix #487, changing TSK_SIMPLIFY_KEEP_UNARY to TSK_SIMPLIFY_KEEP_UNARY_IN_INDIVIDUALS for retainCoalescentOnly=F (a change in simplification behavior) diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 8aeafd8c..dda2520a 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -702,7 +702,7 @@ EidosValue_SP MutationType::ExecuteInstanceMethod(EidosGlobalStringID p_method_i } } -// ********************* - (float$)defaultDominanceForTrait([Nio trait = NULL]) +// ********************* - (float$)defaultDominanceForTrait([Nio trait = NULL]) // EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -735,7 +735,7 @@ EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalSt } } -// ********************* - (fs)effectDistributionParamsForTrait([Nio trait = NULL]) +// ********************* - (fs)effectDistributionParamsForTrait([Nio trait = NULL]) // EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -793,7 +793,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos } } -// ********************* - (string$)effectDistributionTypeForTrait([Nio trait = NULL]) +// ********************* - (string$)effectDistributionTypeForTrait([Nio trait = NULL]) // EidosValue_SP MutationType::ExecuteMethod_effectDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -827,7 +827,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionTypeForTrait(EidosGl return EidosValue_SP(string_result); } -// ********************* - (float)drawEffectForTrait([Nio trait = NULL], [integer$ n = 1]) +// ********************* - (float)drawEffectForTrait([Nio trait = NULL], [integer$ n = 1]) // EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -865,7 +865,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID } } -// ********************* - (void)setDefaultDominanceForTrait(Nio trait, float dominance) +// ********************* - (void)setDefaultDominanceForTrait(Nio trait, float dominance) // EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -914,7 +914,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba return gStaticEidosValueVOID; } -// ********************* - (void)setEffectDistributionForTrait(Nio trait, string$ distributionType, ...) +// ********************* - (void)setEffectDistributionForTrait(Nio trait, string$ distributionType, ...) // EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { diff --git a/core/slim_test_other.cpp b/core/slim_test_other.cpp index 3479ceee..b244401a 100644 --- a/core/slim_test_other.cpp +++ b/core/slim_test_other.cpp @@ -2973,7 +2973,7 @@ initialize() { SLiMAssertScriptSuccess(base_script + "calcTajimasD(sim.subpopulations.haplosomesForChromosomes(2), NULL, 1e5, 2e5); }", __LINE__); SLiMAssertScriptSuccess(base_script + "calcTajimasD(sim.subpopulations.haplosomesForChromosomes(1), muts_ch1, 1e5, 2e5); }", __LINE__); - // (numeric)calcSFS([Ni$ binCount = NULL], [No haplosomes = NULL], [No muts = NULL], [string$ metric = "density"], [logical$ fold = F]) + // (numeric)calcSFS([Ni$ binCount = NULL], [No haplosomes = NULL], [No muts = NULL], [string$ metric = "density"], [logical$ fold = F]) SLiMAssertScriptRaise(base_script + "calcSFS(); }", "when binCount is NULL", __LINE__, true, /* p_error_is_in_stop */ true); SLiMAssertScriptSuccess(base_script + "calcSFS(10); }", __LINE__); SLiMAssertScriptSuccess(base_script + "calcSFS(10, sim.subpopulations.haplosomesForChromosomes(1)); }", __LINE__); From 30eb9a7bd4e7d69e399e2ef972b9c5a046d47f4f Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 10 Oct 2025 14:40:49 -0400 Subject: [PATCH 007/107] shift to slim_effect_t instead of slim_selcoeff_t --- QtSLiM/QtSLiMChromosomeWidget_GL.cpp | 2 +- QtSLiM/QtSLiMChromosomeWidget_QT.cpp | 2 +- SLiMgui/ChromosomeView.mm | 2 +- core/chromosome.cpp | 4 +-- core/haplosome.cpp | 14 ++++---- core/individual.cpp | 10 +++--- core/mutation.cpp | 48 ++++++++++++++-------------- core/mutation.h | 16 +++++----- core/mutation_type.cpp | 8 ++--- core/mutation_type.h | 4 +-- core/population.cpp | 24 +++++++------- core/slim_globals.h | 2 +- core/species.cpp | 22 ++++++------- core/species.h | 2 +- core/substitution.cpp | 4 +-- core/substitution.h | 6 ++-- 16 files changed, 85 insertions(+), 85 deletions(-) diff --git a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp index b156bee2..c0e3241f 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp @@ -265,7 +265,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch // We optimize fixed-DFE mutation types only, and those using a fixed color set by the user if ((ed_info.dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) { - slim_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(ed_info.dfe_parameters_[0])); + slim_effect_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(ed_info.dfe_parameters_[0])); EIDOS_BZERO(heightBuffer, static_cast(displayPixelWidth) * sizeof(int16_t)); diff --git a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp index 03231afd..a5c9baec 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp @@ -261,7 +261,7 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch // We optimize fixed-DFE mutation types only, and those using a fixed color set by the user if ((ed_info.dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) { - slim_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(ed_info.dfe_parameters_[0])); + slim_effect_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(ed_info.dfe_parameters_[0])); EIDOS_BZERO(heightBuffer, static_cast(displayPixelWidth) * sizeof(int16_t)); diff --git a/SLiMgui/ChromosomeView.mm b/SLiMgui/ChromosomeView.mm index e492a883..05f9dcfd 100644 --- a/SLiMgui/ChromosomeView.mm +++ b/SLiMgui/ChromosomeView.mm @@ -706,7 +706,7 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome // We optimize fixed-DFE mutation types only, and those using a fixed color set by the user if ((ed_info.dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) { - slim_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : (slim_selcoeff_t)ed_info.dfe_parameters_[0]); + slim_effect_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : (slim_effect_t)ed_info.dfe_parameters_[0]); EIDOS_BZERO(heightBuffer, displayPixelWidth * sizeof(int16_t)); diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 57a255d5..9c7efdb9 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1035,7 +1035,7 @@ MutationIndex Chromosome::DrawNewMutation(std::pair(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT + slim_effect_t selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE, SINCE WE DO NOT KNOW WHAT HAPLOSOME WE WILL BE INSERTED INTO! THIS IS THE CALLER'S RESPONSIBILITY! MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); @@ -1405,7 +1405,7 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pair(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT + slim_effect_t selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE! THIS IS THE CALLER'S RESPONSIBILITY! MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 7a4730f4..e50e13bf 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2897,7 +2897,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, origin_subpop_id, origin_tick, (int8_t)nucleotide); // FIXME MULTITRAIT + Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, origin_subpop_id, origin_tick, (int8_t)nucleotide); // FIXME MULTITRAIT // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DFE), so we set all_pure_neutral_DFE_ also @@ -3398,7 +3398,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, subpop_index, origin_tick, nucleotide); // FIXME MULTITRAIT + Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, subpop_index, origin_tick, nucleotide); // FIXME MULTITRAIT // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ if (selection_coeff != 0.0) @@ -3721,8 +3721,8 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt // parse/validate the INFO fields that we recognize std::vector info_substrs = Eidos_string_split(info_str, ";"); std::vector info_mutids; - std::vector info_selcoeffs; - std::vector info_domcoeffs; + std::vector info_selcoeffs; + std::vector info_domcoeffs; std::vector info_poporigin; std::vector info_tickorigin; std::vector info_muttype; @@ -3950,7 +3950,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file MT field missing, but no default mutation type was supplied in the mutationType parameter." << EidosTerminate(); // get the dominance coefficient from DOM, or use the default coefficient from the mutation type - slim_selcoeff_t dominance_coeff; + slim_effect_t dominance_coeff; if (info_domcoeffs.size() > 0) dominance_coeff = info_domcoeffs[alt_allele_index]; @@ -3958,12 +3958,12 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt dominance_coeff = mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_; // FIXME MULTITRAIT // get the selection coefficient from S, or draw one from the mutation type - slim_selcoeff_t selection_coeff; + slim_effect_t selection_coeff; if (info_selcoeffs.size() > 0) selection_coeff = info_selcoeffs[alt_allele_index]; else - selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT + selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // get the subpop index from PO, or set to -1; no bounds checking on this slim_objectid_t subpop_index = -1; diff --git a/core/individual.cpp b/core/individual.cpp index 05eb6396..87835785 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -4453,8 +4453,8 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal // parse/validate the INFO fields that we recognize std::vector info_substrs = Eidos_string_split(info_str, ";"); std::vector info_mutids; - std::vector info_selcoeffs; - std::vector info_domcoeffs; + std::vector info_selcoeffs; + std::vector info_domcoeffs; std::vector info_poporigin; std::vector info_tickorigin; std::vector info_muttype; @@ -4579,7 +4579,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file MT field missing, but no default mutation type was supplied in the mutationType parameter." << EidosTerminate(); // get the dominance coefficient from DOM, or use the default coefficient from the mutation type - slim_selcoeff_t dominance_coeff; + slim_effect_t dominance_coeff; if (info_domcoeffs.size() > 0) dominance_coeff = info_domcoeffs[alt_allele_index]; @@ -4587,12 +4587,12 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal dominance_coeff = mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_; // FIXME MULTITRAIT // get the selection coefficient from S, or draw one from the mutation type - slim_selcoeff_t selection_coeff; + slim_effect_t selection_coeff; if (info_selcoeffs.size() > 0) selection_coeff = info_selcoeffs[alt_allele_index]; else - selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT + selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // get the subpop index from PO, or set to -1; no bounds checking on this slim_objectid_t subpop_index = -1; diff --git a/core/mutation.cpp b/core/mutation.cpp index 284200da..c10a0442 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -229,7 +229,7 @@ size_t SLiMMemoryUsageForMutationRefcounts(void) // A global counter used to assign all Mutation objects a unique ID slim_mutationid_t gSLiM_next_mutation_id = 0; -Mutation::Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : +Mutation::Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(p_selection_coeff), dominance_coeff_(p_dominance_coeff), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(gSLiM_next_mutation_id++) { #ifdef DEBUG_LOCKS_ENABLED @@ -240,9 +240,9 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ tag_value_ = SLIM_TAG_UNSET_VALUE; // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); + cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); + cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); // zero out our refcount, which is now kept in a separate buffer gSLiM_Mutation_Refcounts[BlockIndex()] = 0; @@ -281,7 +281,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ std::cout << "Class Mutation memory layout (sizeof(Mutation) == " << sizeof(Mutation) << ") :" << std::endl << std::endl; std::cout << " " << (ptr_mutation_type_ptr_ - ptr_base) << " (" << sizeof(MutationType *) << " bytes): MutationType *mutation_type_ptr_" << std::endl; std::cout << " " << (ptr_position_ - ptr_base) << " (" << sizeof(slim_position_t) << " bytes): const slim_position_t position_" << std::endl; - std::cout << " " << (ptr_selection_coeff_ - ptr_base) << " (" << sizeof(slim_selcoeff_t) << " bytes): slim_selcoeff_t selection_coeff_" << std::endl; + std::cout << " " << (ptr_selection_coeff_ - ptr_base) << " (" << sizeof(slim_effect_t) << " bytes): slim_effect_t selection_coeff_" << std::endl; std::cout << " " << (ptr_subpop_index_ - ptr_base) << " (" << sizeof(slim_objectid_t) << " bytes): slim_objectid_t subpop_index_" << std::endl; std::cout << " " << (ptr_origin_tick_ - ptr_base) << " (" << sizeof(slim_tick_t) << " bytes): const slim_tick_t origin_tick_" << std::endl; std::cout << " " << (ptr_state_ - ptr_base) << " (" << sizeof(int8_t) << " bytes): const int8_t state_" << std::endl; @@ -289,9 +289,9 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ std::cout << " " << (ptr_scratch_ - ptr_base) << " (" << sizeof(int8_t) << " bytes): const int8_t scratch_" << std::endl; std::cout << " " << (ptr_mutation_id_ - ptr_base) << " (" << sizeof(slim_mutationid_t) << " bytes): const slim_mutationid_t mutation_id_" << std::endl; std::cout << " " << (ptr_tag_value_ - ptr_base) << " (" << sizeof(slim_usertag_t) << " bytes): slim_usertag_t tag_value_" << std::endl; - std::cout << " " << (ptr_cached_one_plus_sel_ - ptr_base) << " (" << sizeof(slim_selcoeff_t) << " bytes): slim_selcoeff_t cached_one_plus_sel_" << std::endl; - std::cout << " " << (ptr_cached_one_plus_dom_sel_ - ptr_base) << " (" << sizeof(slim_selcoeff_t) << " bytes): slim_selcoeff_t cached_one_plus_dom_sel_" << std::endl; - std::cout << " " << (ptr_cached_one_plus_haploiddom_sel_ - ptr_base) << " (" << sizeof(slim_selcoeff_t) << " bytes): slim_selcoeff_t cached_one_plus_haploiddom_sel_" << std::endl; + std::cout << " " << (ptr_cached_one_plus_sel_ - ptr_base) << " (" << sizeof(slim_effect_t) << " bytes): slim_effect_t cached_one_plus_sel_" << std::endl; + std::cout << " " << (ptr_cached_one_plus_dom_sel_ - ptr_base) << " (" << sizeof(slim_effect_t) << " bytes): slim_effect_t cached_one_plus_dom_sel_" << std::endl; + std::cout << " " << (ptr_cached_one_plus_haploiddom_sel_ - ptr_base) << " (" << sizeof(slim_effect_t) << " bytes): slim_effect_t cached_one_plus_haploiddom_sel_" << std::endl; std::cout << std::endl; been_here = true; @@ -300,16 +300,16 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ #endif } -Mutation::Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : +Mutation::Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(p_selection_coeff), dominance_coeff_(p_dominance_coeff), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id) { // initialize the tag to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); + cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); + cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); // zero out our refcount, which is now kept in a separate buffer gSLiM_Mutation_Refcounts[BlockIndex()] = 0; @@ -739,9 +739,9 @@ EidosValue_SP Mutation::ExecuteMethod_setSelectionCoeff(EidosGlobalStringID p_me EidosValue *selectionCoeff_value = p_arguments[0].get(); double value = selectionCoeff_value->FloatAtIndex_NOCAST(0, nullptr); - slim_selcoeff_t old_coeff = selection_coeff_; + slim_effect_t old_coeff = selection_coeff_; - selection_coeff_ = static_cast(value); + selection_coeff_ = static_cast(value); // intentionally no lower or upper bound; -1.0 is lethal, but DFEs may generate smaller values, and we don't want to prevent or bowdlerize that // also, the dominance coefficient modifies the selection coefficient, so values < -1 are in fact meaningfully different @@ -768,9 +768,9 @@ EidosValue_SP Mutation::ExecuteMethod_setSelectionCoeff(EidosGlobalStringID p_me } // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); + cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); + cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); return gStaticEidosValueVOID; } @@ -784,12 +784,12 @@ EidosValue_SP Mutation::ExecuteMethod_setDominanceCoeff(EidosGlobalStringID p_me double value = dominanceCoeff_value->FloatAtIndex_NOCAST(0, nullptr); - dominance_coeff_ = static_cast(value); // intentionally no bounds check + dominance_coeff_ = static_cast(value); // intentionally no bounds check // cache values used by the fitness calculation code for speed; see header - //cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - //cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + //cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); + cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); + //cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); return gStaticEidosValueVOID; } @@ -815,9 +815,9 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth mutation_type_ptr_->all_pure_neutral_DFE_ = false; // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); + cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); + cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); return gStaticEidosValueVOID; } diff --git a/core/mutation.h b/core/mutation.h index 99b8edc9..66157362 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -76,8 +76,8 @@ class Mutation : public EidosDictionaryRetained MutationType *mutation_type_ptr_; // mutation type identifier const slim_position_t position_; // position on the chromosome - slim_selcoeff_t selection_coeff_; // selection coefficient (s) - slim_selcoeff_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t selection_coeff_; // selection coefficient (s) + slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default slim_objectid_t subpop_index_; // subpopulation in which mutation arose (or a user-defined tag value!) const slim_tick_t origin_tick_; // tick in which the mutation arose slim_chromosome_index_t chromosome_index_; // the (uint8_t) index of this mutation's chromosome @@ -95,10 +95,10 @@ class Mutation : public EidosDictionaryRetained // We cache values used in the fitness calculation code, for speed. These are the final fitness effects of this mutation // when it is homozygous or heterozygous, respectively. These values are clamped to a minimum of 0.0, so that multiplying // by them cannot cause the fitness of the individual to go below 0.0, avoiding slow tests in the core fitness loop. These - // values use slim_selcoeff_t for speed; roundoff should not be a concern, since such differences would be inconsequential. - slim_selcoeff_t cached_one_plus_sel_; // a cached value for (1 + selection_coeff_), clamped to 0.0 minimum - slim_selcoeff_t cached_one_plus_dom_sel_; // a cached value for (1 + dominance_coeff * selection_coeff_), clamped to 0.0 minimum - slim_selcoeff_t cached_one_plus_hemizygousdom_sel_; // a cached value for (1 + hemizygous_dominance_coeff_ * selection_coeff_), clamped to 0.0 minimum + // values use slim_effect_t for speed; roundoff should not be a concern, since such differences would be inconsequential. + slim_effect_t cached_one_plus_sel_; // a cached value for (1 + selection_coeff_), clamped to 0.0 minimum + slim_effect_t cached_one_plus_dom_sel_; // a cached value for (1 + dominance_coeff * selection_coeff_), clamped to 0.0 minimum + slim_effect_t cached_one_plus_hemizygousdom_sel_; // a cached value for (1 + hemizygous_dominance_coeff_ * selection_coeff_), clamped to 0.0 minimum #if DEBUG mutable slim_refcount_t refcount_CHECK_; // scratch space for checking of parallel refcounting @@ -107,8 +107,8 @@ class Mutation : public EidosDictionaryRetained Mutation(const Mutation&) = delete; // no copying Mutation& operator=(const Mutation&) = delete; // no copying Mutation(void) = delete; // no null construction; Mutation is an immutable class - Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); - Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); + Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); + Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline #if DEBUG_MUTATIONS diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index dda2520a..17bbdd69 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -96,7 +96,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr if (trait_index == 0) { // the DE for the first trait gets set from the parameters passed in - ed_info.default_dominance_coeff_ = static_cast(p_dominance_coeff); + ed_info.default_dominance_coeff_ = static_cast(p_dominance_coeff); ed_info.dfe_type_ = p_dfe_type; ed_info.dfe_parameters_ = std::move(p_dfe_parameters); ed_info.dfe_strings_ = std::move(p_dfe_strings); @@ -593,7 +593,7 @@ void MutationType::SetProperty(EidosGlobalStringID p_property_id, const EidosVal { double value = p_value.FloatAtIndex_NOCAST(0, nullptr); - hemizygous_dominance_coeff_ = static_cast(value); // intentionally no bounds check + hemizygous_dominance_coeff_ = static_cast(value); // intentionally no bounds check // Changing the hemizygous dominance coefficient means that the cached fitness effects of all mutations using this type // become invalid. We set a flag here to indicate that values that depend on us need to be recached. @@ -887,7 +887,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba { EffectDistributionInfo &de_info = effect_distributions_[trait_index]; - de_info.default_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check + de_info.default_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check } } else if (dominance_count == (int)trait_indices.size()) @@ -898,7 +898,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba EffectDistributionInfo &de_info = effect_distributions_[trait_index]; double dominance = dominance_value->FloatAtIndex_NOCAST(dominance_index, nullptr); - de_info.default_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check + de_info.default_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check } } else diff --git a/core/mutation_type.h b/core/mutation_type.h index a0fc509c..9436849b 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -63,7 +63,7 @@ std::ostream& operator<<(std::ostream& p_out, DFEType p_dfe_type); // This struct holds information about a distribution of effects (including dominance) for one trait. // MutationEffect then keeps a vector of these structs, one for each trait. typedef struct _EffectDistributionInfo { - slim_selcoeff_t default_dominance_coeff_; // the default dominance coefficient (h) inherited by mutations of this type + slim_effect_t default_dominance_coeff_; // the default dominance coefficient (h) inherited by mutations of this type DFEType dfe_type_; // distribution of fitness effects (DFE) type (f: fixed, g: gamma, e: exponential, n: normal, w: Weibull) std::vector dfe_parameters_; // DFE parameters, of type double (originally float or integer type) @@ -98,7 +98,7 @@ class MutationType : public EidosDictionaryUnretained std::vector effect_distributions_; // DEs for each trait in the species - slim_selcoeff_t hemizygous_dominance_coeff_; // dominance coefficient (h) used when one haplosome is null + slim_effect_t hemizygous_dominance_coeff_; // dominance coefficient (h) used when one haplosome is null bool nucleotide_based_; // if true, the mutation type is nucleotide-based (i.e. mutations keep associated nucleotides) diff --git a/core/population.cpp b/core/population.cpp index 289a448d..dfc55a9a 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5195,13 +5195,13 @@ void Population::ValidateMutationFitnessCaches(void) { MutationIndex mut_index = (*registry_iter++); Mutation *mut = mut_block_ptr + mut_index; - slim_selcoeff_t sel_coeff = mut->selection_coeff_; - slim_selcoeff_t dom_coeff = mut->dominance_coeff_; - slim_selcoeff_t hemizygous_dom_coeff = mut->mutation_type_ptr_->hemizygous_dominance_coeff_; + slim_effect_t sel_coeff = mut->selection_coeff_; + slim_effect_t dom_coeff = mut->dominance_coeff_; + slim_effect_t hemizygous_dom_coeff = mut->mutation_type_ptr_->hemizygous_dominance_coeff_; - mut->cached_one_plus_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + sel_coeff); - mut->cached_one_plus_dom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + dom_coeff * sel_coeff); - mut->cached_one_plus_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + hemizygous_dom_coeff * sel_coeff); + mut->cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + sel_coeff); + mut->cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dom_coeff * sel_coeff); + mut->cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + hemizygous_dom_coeff * sel_coeff); } } @@ -7896,7 +7896,7 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit int32_t slim_objectid_t_size = sizeof(slim_objectid_t); int32_t slim_popsize_t_size = sizeof(slim_popsize_t); int32_t slim_refcount_t_size = sizeof(slim_refcount_t); - int32_t slim_selcoeff_t_size = sizeof(slim_selcoeff_t); + int32_t slim_effect_t_size = sizeof(slim_effect_t); int32_t slim_mutationid_t_size = sizeof(slim_mutationid_t); // Added in version 2 int32_t slim_polymorphismid_t_size = sizeof(slim_polymorphismid_t); // Added in version 2 int32_t slim_age_t_size = sizeof(slim_age_t); // Added in version 6 @@ -7909,7 +7909,7 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit p_out.write(reinterpret_cast(&slim_objectid_t_size), sizeof slim_objectid_t_size); p_out.write(reinterpret_cast(&slim_popsize_t_size), sizeof slim_popsize_t_size); p_out.write(reinterpret_cast(&slim_refcount_t_size), sizeof slim_refcount_t_size); - p_out.write(reinterpret_cast(&slim_selcoeff_t_size), sizeof slim_selcoeff_t_size); + p_out.write(reinterpret_cast(&slim_effect_t_size), sizeof slim_effect_t_size); p_out.write(reinterpret_cast(&slim_mutationid_t_size), sizeof slim_mutationid_t_size); // Added in version 2 p_out.write(reinterpret_cast(&slim_polymorphismid_t_size), sizeof slim_polymorphismid_t_size); // Added in version 2 p_out.write(reinterpret_cast(&slim_age_t_size), sizeof slim_age_t_size); // Added in version 6 @@ -8130,8 +8130,8 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit int64_t mutation_id = mutation_ptr->mutation_id_; // Added in version 2 slim_objectid_t mutation_type_id = mutation_type_ptr->mutation_type_id_; slim_position_t position = mutation_ptr->position_; - slim_selcoeff_t selection_coeff = mutation_ptr->selection_coeff_; - slim_selcoeff_t dominance_coeff = mutation_ptr->dominance_coeff_; + slim_effect_t selection_coeff = mutation_ptr->selection_coeff_; + slim_effect_t dominance_coeff = mutation_ptr->dominance_coeff_; // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved; too edge to be bothered... slim_objectid_t subpop_index = mutation_ptr->subpop_index_; slim_tick_t origin_tick = mutation_ptr->origin_tick_; @@ -8287,8 +8287,8 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit int64_t mutation_id = substitution_ptr->mutation_id_; slim_objectid_t mutation_type_id = mutation_type_ptr->mutation_type_id_; slim_position_t position = substitution_ptr->position_; - slim_selcoeff_t selection_coeff = substitution_ptr->selection_coeff_; - slim_selcoeff_t dominance_coeff = substitution_ptr->dominance_coeff_; + slim_effect_t selection_coeff = substitution_ptr->selection_coeff_; + slim_effect_t dominance_coeff = substitution_ptr->dominance_coeff_; slim_objectid_t subpop_index = substitution_ptr->subpop_index_; slim_tick_t origin_tick = substitution_ptr->origin_tick_; slim_tick_t fixation_tick = substitution_ptr->fixation_tick_; diff --git a/core/slim_globals.h b/core/slim_globals.h index d128e0ec..ca2dc2bb 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -124,7 +124,7 @@ typedef int64_t slim_mutationid_t; // identifiers for mutations, which require typedef int64_t slim_pedigreeid_t; // identifiers for pedigreed individuals; over many ticks in a large model maybe 64 bits? typedef int64_t slim_haplosomeid_t; // identifiers for pedigreed haplosomes; not user-visible, used by the tree-recording code, pedigree_id*2 + [0/1] typedef int32_t slim_polymorphismid_t; // identifiers for polymorphisms, which need only 32 bits since they are only segregating mutations -typedef float slim_selcoeff_t; // storage of selection coefficients in memory-tight classes; also dominance coefficients +typedef float slim_effect_t; // storage of trait effects (e.g., selection coefficients) in memory-tight classes; also dominance coefficients #define SLIM_MAX_TICK (1000000000L) // ticks range from 0 (init time) to this; SLIM_MAX_TICK + 1 is an "infinite" marker value #define SLIM_MAX_BASE_POSITION (1000000000000000L) // base positions in the chromosome can range from 0 to 1e15; see above diff --git a/core/species.cpp b/core/species.cpp index 980518e6..3a5060c6 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1338,10 +1338,10 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos slim_position_t position = SLiMCastToPositionTypeOrRaise(position_long); iss >> sub; - slim_selcoeff_t selection_coeff = static_cast(EidosInterpreter::FloatForString(sub, nullptr)); + slim_effect_t selection_coeff = static_cast(EidosInterpreter::FloatForString(sub, nullptr)); iss >> sub; - slim_selcoeff_t dominance_coeff = static_cast(EidosInterpreter::FloatForString(sub, nullptr)); + slim_effect_t dominance_coeff = static_cast(EidosInterpreter::FloatForString(sub, nullptr)); iss >> sub; slim_objectid_t subpop_index = SLiMEidosScript::ExtractIDFromStringWithPrefix(sub, 'p', nullptr); @@ -1651,8 +1651,8 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid int32_t double_size; double double_test; int64_t flags = 0; - int32_t slim_tick_t_size, slim_position_t_size, slim_objectid_t_size, slim_popsize_t_size, slim_refcount_t_size, slim_selcoeff_t_size, slim_mutationid_t_size, slim_polymorphismid_t_size, slim_age_t_size, slim_pedigreeid_t_size, slim_haplosomeid_t_size, slim_usertag_t_size; - int header_length = sizeof(double_size) + sizeof(double_test) + sizeof(flags) + sizeof(slim_tick_t_size) + sizeof(slim_position_t_size) + sizeof(slim_objectid_t_size) + sizeof(slim_popsize_t_size) + sizeof(slim_refcount_t_size) + sizeof(slim_selcoeff_t_size) + sizeof(slim_mutationid_t_size) + sizeof(slim_polymorphismid_t_size) + sizeof(slim_age_t_size) + sizeof(slim_pedigreeid_t_size) + sizeof(slim_haplosomeid_t_size) + sizeof(slim_usertag_t_size) + sizeof(file_tick) + sizeof(file_cycle) + sizeof(section_end_tag); + int32_t slim_tick_t_size, slim_position_t_size, slim_objectid_t_size, slim_popsize_t_size, slim_refcount_t_size, slim_effect_t_size, slim_mutationid_t_size, slim_polymorphismid_t_size, slim_age_t_size, slim_pedigreeid_t_size, slim_haplosomeid_t_size, slim_usertag_t_size; + int header_length = sizeof(double_size) + sizeof(double_test) + sizeof(flags) + sizeof(slim_tick_t_size) + sizeof(slim_position_t_size) + sizeof(slim_objectid_t_size) + sizeof(slim_popsize_t_size) + sizeof(slim_refcount_t_size) + sizeof(slim_effect_t_size) + sizeof(slim_mutationid_t_size) + sizeof(slim_polymorphismid_t_size) + sizeof(slim_age_t_size) + sizeof(slim_pedigreeid_t_size) + sizeof(slim_haplosomeid_t_size) + sizeof(slim_usertag_t_size) + sizeof(file_tick) + sizeof(file_cycle) + sizeof(section_end_tag); // this is how to add more header tags in future versions //if (file_version >= 9) @@ -1693,8 +1693,8 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid memcpy(&slim_refcount_t_size, p, sizeof(slim_refcount_t_size)); p += sizeof(slim_refcount_t_size); - memcpy(&slim_selcoeff_t_size, p, sizeof(slim_selcoeff_t_size)); - p += sizeof(slim_selcoeff_t_size); + memcpy(&slim_effect_t_size, p, sizeof(slim_effect_t_size)); + p += sizeof(slim_effect_t_size); memcpy(&slim_mutationid_t_size, p, sizeof(slim_mutationid_t_size)); p += sizeof(slim_mutationid_t_size); @@ -1750,7 +1750,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid (slim_objectid_t_size != sizeof(slim_objectid_t)) || (slim_popsize_t_size != sizeof(slim_popsize_t)) || (slim_refcount_t_size != sizeof(slim_refcount_t)) || - (slim_selcoeff_t_size != sizeof(slim_selcoeff_t)) || + (slim_effect_t_size != sizeof(slim_effect_t)) || (slim_mutationid_t_size != sizeof(slim_mutationid_t)) || (slim_polymorphismid_t_size != sizeof(slim_polymorphismid_t)) || (slim_age_t_size != sizeof(slim_age_t)) || @@ -2061,8 +2061,8 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid slim_mutationid_t mutation_id; slim_objectid_t mutation_type_id; slim_position_t position; - slim_selcoeff_t selection_coeff; - slim_selcoeff_t dominance_coeff; + slim_effect_t selection_coeff; + slim_effect_t dominance_coeff; slim_objectid_t subpop_index; slim_tick_t tick; slim_refcount_t prevalence; @@ -2362,8 +2362,8 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid slim_mutationid_t mutation_id; slim_objectid_t mutation_type_id; slim_position_t position; - slim_selcoeff_t selection_coeff; - slim_selcoeff_t dominance_coeff; + slim_effect_t selection_coeff; + slim_effect_t dominance_coeff; slim_objectid_t subpop_index; slim_tick_t origin_tick; slim_tick_t fixation_tick; diff --git a/core/species.h b/core/species.h index 9956259b..5742c803 100644 --- a/core/species.h +++ b/core/species.h @@ -96,7 +96,7 @@ enum class SLiMFileFormat typedef struct __attribute__((__packed__)) { slim_objectid_t mutation_type_id_; // 4 bytes (int32_t): the id of the mutation type the mutation belongs to - slim_selcoeff_t selection_coeff_; // 4 bytes (float): the selection coefficient + slim_effect_t selection_coeff_; // 4 bytes (float): the mutation effect (e.g., selection coefficient) // FIXME MULTITRAIT need to add a dominance_coeff_ property here! slim_objectid_t subpop_index_; // 4 bytes (int32_t): the id of the subpopulation in which the mutation arose slim_tick_t origin_tick_; // 4 bytes (int32_t): the tick in which the mutation arose diff --git a/core/substitution.cpp b/core/substitution.cpp index cc4bebce..58f51b4f 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -42,8 +42,8 @@ Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : // No call to ContentsChanged() here; we know we use Dictionary not DataFrame, and Mutation already vetted the dictionary } -Substitution::Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide) : -mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(static_cast(p_selection_coeff)), dominance_coeff_(static_cast(p_dominance_coeff)), subpop_index_(p_subpop_index), origin_tick_(p_tick), fixation_tick_(p_fixation_tick), chromosome_index_(p_chromosome_index), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id), tag_value_(SLIM_TAG_UNSET_VALUE) +Substitution::Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide) : +mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(static_cast(p_selection_coeff)), dominance_coeff_(static_cast(p_dominance_coeff)), subpop_index_(p_subpop_index), origin_tick_(p_tick), fixation_tick_(p_fixation_tick), chromosome_index_(p_chromosome_index), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id), tag_value_(SLIM_TAG_UNSET_VALUE) { } diff --git a/core/substitution.h b/core/substitution.h index 142d2abd..7453ac79 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -49,8 +49,8 @@ class Substitution : public EidosDictionaryRetained MutationType *mutation_type_ptr_; // mutation type identifier slim_position_t position_; // position - slim_selcoeff_t selection_coeff_; // selection coefficient (s) - slim_selcoeff_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t selection_coeff_; // selection coefficient (s) + slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default slim_objectid_t subpop_index_; // subpopulation in which mutation arose slim_tick_t origin_tick_; // tick in which mutation arose slim_tick_t fixation_tick_; // tick in which mutation fixed @@ -63,7 +63,7 @@ class Substitution : public EidosDictionaryRetained Substitution& operator=(const Substitution&) = delete; // no copying Substitution(void) = delete; // no null construction Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick); // construct from the mutation that has fixed, and the tick in which it fixed - Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_selcoeff_t p_selection_coeff, slim_selcoeff_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide); + Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide); // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline inline virtual ~Substitution(void) override { } From 27eaeccec00ef9dec906ccb7955411d739385751 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 10 Oct 2025 21:02:27 -0400 Subject: [PATCH 008/107] add support for individual offsets in Individual --- QtSLiM/help/SLiMHelpClasses.html | 5 + SLiMgui/SLiMHelpClasses.rtf | 49 ++++++++ VERSIONS | 3 + core/individual.cpp | 207 +++++++++++++++++++++++++++++++ core/individual.h | 9 +- core/slim_globals.cpp | 2 + core/slim_globals.h | 4 + core/trait.h | 2 + 8 files changed, 280 insertions(+), 1 deletion(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index f528ea45..7d6e7493 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -467,6 +467,8 @@

The chromosomes parameter may be NULL, or may provide a vector of chromosomes specified by their integer id, string symbol, or with the Chromosome object itself.  If chromosomes is NULL (the default), mutations associated with every chromosome are returned; no filtering by chromosome is done.  Otherwise, only mutations associated with the specified chromosomes will be returned.

The returned vector will contain tranches of mutations, one tranche per chromosome, in the order that the chromosomes were specified (if chromosomes is non-NULL) or the order the chromosomes were defined in the model (if chromosomes is NULL).  Within a given tranche, the mutations for that chromosome will be returned in sorted order by position.  (If more than one mutation associated with a given chromosome exists at the same position, the order in which those mutations are returned is undefined.)

This method replaces the deprecated method uniqueMutationsOfType(), while providing additional useful options.  It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.

+

– (float)offsetForTrait([Nio<Trait> trait = NULL])

+

Returns the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+ (void)outputIndividuals([Ns$ filePath = NULL], [logical$ append = F], [Niso<Chromosome>$ chromosome = NULL], [logical$ spatialPositions = T], [logical$ ages = T], [logical$ ancestralNucleotides = F], [logical$ pedigreeIDs = F], [logical$ objectTags = F])

Output the state of the target vector of individuals in SLiM's own format.  If the optional parameter filePath is NULL (the default), output will be sent to Eidos’s output stream.  Otherwise, output will be sent to the filesystem path specified by filePath, overwriting that file if append if F, or appending to the end of it if append is T.  This method is quite similar to the Species method outputFull(), but (1) it can produce output for any vector of individuals, not always for the entire population; (2) it does not support output in a binary format; (3) it can produce output regarding the genetics for all chromosomes or for just one focal chromosome; and (4) there is no corresponding read method, as readFromPopulationFile() can read the data saved by outputFull().

The chromosome parameter specifies a focal chromosome for which the genetics of the target individuals will be output.  If chromosome is NULL, all chromosomes will be output; otherwise, chromosome may specify the focal chromosome with an integer chromosome id, a string chromosome symbol, or a Chromosome object.

@@ -494,6 +496,9 @@

Note that this relatedness is simply pedigree-based relatedness, and does not necessarily correspond to genetic relatedness, because of the effects of factors like assortment and recombination.  If a metric of actual genetic relatedness is desired, tree-sequence recording can be used after simulation is complete, to compute the exact genetic relatedness between individuals based upon the complete ancestry tree (a topic which is beyond the scope of this manual).  Actual genetic relatedness cannot presently be calculated during a simulation run; the information is implicitly contained in the recorded tree-sequence tables, but calculating it is too computationally expensive to be reasonable.

This method assumes that the grandparents (or the parents, if grandparental information is not available) are themselves unrelated and that they are not inbred; this assumption is necessary because we have no information about their parentage, since SLiM’s pedigree tracking information only goes back two generations.  Be aware that in a model where inbreeding or selfing occurs at all (including “incidental selfing”, where a hermaphroditic individual happens to choose itself as a mate), some level of “background relatedness” will be present and this assumption will be violated.  In such circumstances, relatedness() will therefore tend to underestimate the degree of relatedness between individuals, and the greater the degree of inbreeding, the greater the underestimation will be.  If inbreeding is allowed in a model – and particularly if it is common – the results of relatedness() should therefore not be taken as an estimate of absolute relatedness, but can still be useful as an estimate of relative relatedness (indicating that, say, A appears from the information available to be more closely related to B than it is to C).

See also sharedParentCount() for a different metric of relatedness.

+

– (void)setOffsetForTrait([Nio<Trait> trait = NULL], [Nif offset = NULL])

+

Sets the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

The parameter offset must follow one of four patterns.  In the first pattern, offset is NULL; this sets the offset for each of the specified traits to its default value (0.0 for additive traits, 1.0 for multiplicative traits) in each target individual.  In the second pattern, offset is a singleton value; this sets the given offset for each of the specified traits in each target individual.  In the third pattern, offset is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual.  In the fourth pattern, offset is of length equal to the number of specified traits times the number of target individuals; this uses offset to provide a different offset value for each trait in each individual, using consecutive values from offset to set the offset for each of the specified traits in one individual before moving to the next individual.

+ (void)setSpatialPosition(float position)

Sets the spatial position of the individual (as accessed through the spatialPosition property).  The length of position (the number of coordinates in the spatial position of an individual) depends upon the spatial dimensionality declared with initializeSLiMOptions().  If the spatial dimensionality is zero (as it is by default), it is an error to call this method.  The elements of position are set into the values of the x, y, and z properties (if those properties are encompassed by the spatial dimensionality of the simulation).  In other words, if the declared dimensionality is "xy", calling individual.setSpatialPosition(c(1.0, 0.5)) property is equivalent to individual.x = 1.0; individual.y = 0.5; individual.z is not set (even if a third value is supplied in position) since it is not encompassed by the simulation’s dimensionality in this example.

Note that this is an Eidos class method, somewhat unusually, which allows it to work in a special way when called on a vector of individuals.  When the target vector of individuals is non-singleton, this method can do one of two things.  If position contains just a single point (i.e., is equal in length to the spatial dimensionality of the model), the spatial position of all of the target individuals will be set to the given point.  Alternatively, if position contains one point per target individual (i.e., is equal in length to the number of individuals multiplied by the spatial dimensionality of the model), the spatial position of each target individual will be set to the corresponding point from position (where the point data is concatenated, not interleaved, just as it would be returned by accessing the spatialPosition property on the vector of target individuals).  Calling this method with a position vector of any other length is an error.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index a7d8ea5a..b28ccfcb 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -3797,6 +3797,22 @@ This method replaces the deprecated method \f4\fs20 , while providing additional useful options. It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(float)offsetForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the individual offset(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 +\'a0(void)outputIndividuals([Ns$\'a0filePath\'a0=\'a0NULL], [logical$\'a0append\'a0=\'a0F], [Niso$\'a0chromosome\'a0=\'a0NULL], [logical$\'a0spatialPositions\'a0=\'a0T], [logical$\'a0ages\'a0=\'a0T], [logical$\'a0ancestralNucleotides\'a0=\'a0F], [logical$\'a0pedigreeIDs\'a0=\'a0F], [logical$\'a0objectTags\'a0=\'a0F])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 @@ -4155,6 +4171,39 @@ See also \f4\fs20 for a different metric of relatedness.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(void)setOffsetForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif offset = NULL])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Sets the individual offset(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 offset +\f4\fs20 must follow one of four patterns. In the first pattern, offset is +\f3\fs18 NULL +\f4\fs20 ; this sets the offset for each of the specified traits to its default value ( +\f3\fs18 0.0 +\f4\fs20 for additive traits, +\f3\fs18 1.0 +\f4\fs20 for multiplicative traits) in each target individual. In the second pattern, +\f3\fs18 offset +\f4\fs20 is a singleton value; this sets the given offset for each of the specified traits in each target individual. In the third pattern, +\f3\fs18 offset +\f4\fs20 is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual. In the fourth pattern, +\f3\fs18 offset +\f4\fs20 is of length equal to the number of specified traits times the number of target individuals; this uses +\f3\fs18 offset +\f4\fs20 to provide a different offset value for each trait in each individual, using consecutive values from +\f3\fs18 offset +\f4\fs20 to set the offset for each of the specified traits in one individual before moving to the next individual.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \expnd0\expndtw0\kerning0 +\'a0(void)setSpatialPosition(float\'a0position)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 diff --git a/VERSIONS b/VERSIONS index 20d0e163..e91ab424 100644 --- a/VERSIONS +++ b/VERSIONS @@ -52,6 +52,9 @@ multitrait branch: added C++ IsPureNeutralDFE() method to represent whether all of the effects of a given mutation type are all neutral add Individual properties for each trait in the individual's species, allowing direct access this was done by adding GetProperty_NO_SIGNATURE() / SetProperty_NO_SIGNATURE() methods called by EidosValue_Object::GetPropertyOfElements() and EidosValue_Object::SetPropertyOfElements() to support properties with no signature + add support in Individual for the individual's offset for each trait + add -(float)offsetForTrait([Nio trait = NULL]) + add +(void)setOffsetForTrait([Nio trait = NULL], [Nif offset = NULL]) version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index 87835785..427c3b5e 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -82,6 +82,30 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu haplosomes_ = (Haplosome **)calloc(haplosome_count_per_individual, sizeof(Haplosome *)); } + // Set up per-trait information such as phenotype caches and individual offsets + Species &species = subpopulation_->species_; + std::vector &traits = species.traits_; + int trait_count = (int)traits.size(); + + if (trait_count == 1) + { + // FIXME MULTITRAIT: DefaultOffset() has a branch; maybe better for each trait to have a cached slim_effect_t for it? or maybe not? + offsets_for_traits_ = &offset_for_trait_0_; + offset_for_trait_0_ = traits[0]->DefaultOffset(); + } + else if (trait_count == 0) + { + offsets_for_traits_ = nullptr; + } + else + { + // FIXME MULTITRAIT: we could keep a buffer of default trait values in the Species, and just memcpy() here + offsets_for_traits_ = static_cast(malloc(trait_count * sizeof(slim_effect_t))); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + offsets_for_traits_[trait_index] = traits[trait_index]->DefaultOffset(); + } + // Initialize tag values to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; tagF_value_ = SLIM_TAGF_UNSET_VALUE; @@ -131,8 +155,12 @@ Individual::~Individual(void) if (haplosomes_ != hapbuffer_) free(haplosomes_); + if (offsets_for_traits_ != &offset_for_trait_0_) + free(offsets_for_traits_); + #if DEBUG haplosomes_ = nullptr; + offsets_for_traits_ = nullptr; #endif } @@ -2891,6 +2919,7 @@ EidosValue_SP Individual::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, case gID_containsMutations: return ExecuteMethod_containsMutations(p_method_id, p_arguments, p_interpreter); //case gID_countOfMutationsOfType: return ExecuteMethod_Accelerated_countOfMutationsOfType(p_method_id, p_arguments, p_interpreter); case gID_haplosomesForChromosomes: return ExecuteMethod_haplosomesForChromosomes(p_method_id, p_arguments, p_interpreter); + case gID_offsetForTrait: return ExecuteMethod_offsetForTrait(p_method_id, p_arguments, p_interpreter); case gID_relatedness: return ExecuteMethod_relatedness(p_method_id, p_arguments, p_interpreter); case gID_sharedParentCount: return ExecuteMethod_sharedParentCount(p_method_id, p_arguments, p_interpreter); //case gID_sumOfMutationsOfType: return ExecuteMethod_Accelerated_sumOfMutationsOfType(p_method_id, p_arguments, p_interpreter); @@ -3071,7 +3100,40 @@ EidosValue_SP Individual::ExecuteMethod_haplosomesForChromosomes(EidosGlobalStri return EidosValue_SP(vec); } + +// ********************* - (float)offsetForTrait([Nio trait = NULL]) +// +EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = subpopulation_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "offsetForTrait"); + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t offset = offsets_for_traits_[trait_index]; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(offset)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t offset = offsets_for_traits_[trait_index]; + + float_result->push_float_no_check(offset); + } + + return EidosValue_SP(float_result); + } +} + // ********************* - (float)relatedness(object individuals, [Niso$ chromosome = NULL]) // EidosValue_SP Individual::ExecuteMethod_relatedness(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -3920,6 +3982,8 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_countOfMutationsOfType, kEidosValueMaskInt | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_countOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_relatedness, kEidosValueMaskFloat))->AddObject("individuals", gSLiM_Individual_Class)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, "chromosome", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_haplosomesForChromosomes, kEidosValueMaskObject, gSLiM_Haplosome_Class))->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddInt_OSN("index", gStaticEidosValueNULL)->AddLogical_OS("includeNulls", gStaticEidosValue_LogicalT)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_uniqueMutationsOfType, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->MarkDeprecated()); @@ -3940,6 +4004,7 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ { switch (p_method_id) { + case gID_setOffsetForTrait: return ExecuteMethod_setOffsetForTrait(p_method_id, p_target, p_arguments, p_interpreter); case gID_outputIndividuals: return ExecuteMethod_outputIndividuals(p_method_id, p_target, p_arguments, p_interpreter); case gID_outputIndividualsToVCF: return ExecuteMethod_outputIndividualsToVCF(p_method_id, p_target, p_arguments, p_interpreter); case gID_readIndividualsFromVCF: return ExecuteMethod_readIndividualsFromVCF(p_method_id, p_target, p_arguments, p_interpreter); @@ -3956,6 +4021,148 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ } } +// ********************* + (void)setOffsetForTrait([Nio trait = NULL], [Nif offset = NULL]) +// +EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *offset_value = p_arguments[1].get(); + + int individuals_count = p_target->Count(); + int offset_count = offset_value->Count(); + + if (individuals_count == 0) + return gStaticEidosValueVOID; + + Individual **individuals_buffer = (Individual **)p_target->ObjectData(); + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForIndividuals(p_target); + + if (!species) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires that all individuals belong to the same species." << EidosTerminate(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "offsetForTrait"); + int trait_count = (int)trait_indices.size(); + + if (offset_value->Type() == EidosValueType::kValueNULL) + { + // pattern 1: setting the default offset value for each trait in one or more individuals + for (int64_t trait_index : trait_indices) + { + Trait *trait = species->traits_[trait_index]; + slim_effect_t offset = trait->DefaultOffset(); + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + ind->offsets_for_traits_[trait_index] = offset; + } + } + } + else if (offset_count == 1) + { + // pattern 2: setting a single offset value across one or more traits in one or more individuals + slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(0, nullptr)); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->offsets_for_traits_[trait_index] = offset; + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->offsets_for_traits_[trait_index] = offset; + } + } + } + else if (offset_count == trait_count) + { + // pattern 3: setting one offset value per trait, in one or more individuals + int offset_index = 0; + + for (int64_t trait_index : trait_indices) + { + slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(offset_index++, nullptr)); + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + ind->offsets_for_traits_[trait_index] = offset; + } + } + } + else if (offset_count == trait_count * individuals_count) + { + // pattern 4: setting different offset values for each trait in each individual; in this case, + // all offsets for the specified traits in a given individual are given consecutively + if (offset_value->Type() == EidosValueType::kValueInt) + { + // integer offset values + const int64_t *offsets_int = offset_value->IntData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->offsets_for_traits_[trait_index] = static_cast(*(offsets_int++)); + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->offsets_for_traits_[trait_index] = static_cast(*(offsets_int++)); + } + } + } + else + { + // float offset values + const double *offsets_float = offset_value->FloatData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->offsets_for_traits_[trait_index] = static_cast(*(offsets_float++)); + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->offsets_for_traits_[trait_index] = static_cast(*(offsets_float++)); + } + } + } + } + else + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires that offset be (a) NULL, requesting the default offset value for each trait, (b) singleton, providing one offset value for all traits, (c) equal in length to the number of traits in the species, providing one offset value per trait, or (d) equal in length to the number of traits times the number of target individuals, providing one offset value per trait per individual." << EidosTerminate(); + + return gStaticEidosValueVOID; +} + // ********************* + (void)outputIndividuals([Ns$ filePath = NULL], [logical$ append=F], [Niso$ chromosome = NULL], [logical$ spatialPositions = T], [logical$ ages = T], [logical$ ancestralNucleotides = F], [logical$ pedigreeIDs = F], [logical$ objectTags = F]) // EidosValue_SP Individual_Class::ExecuteMethod_outputIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const diff --git a/core/individual.h b/core/individual.h index cf3084fa..7eb7462c 100644 --- a/core/individual.h +++ b/core/individual.h @@ -143,13 +143,18 @@ class Individual : public EidosDictionaryUnretained // that confuses interpretation; note that individual_cached_fitness_OVERRIDE_ is not relevant to this #endif - Haplosome *hapbuffer_[2]; // *(hapbuffer_[2]), an internal buffer used to avoid allocation and increase memory nonlocality + Haplosome *hapbuffer_[2]; // *(hapbuffer_[2]), an internal buffer used to avoid allocation and increase memory locality Haplosome **haplosomes_; // OWNED haplosomes; can point to hapbuffer_ or to an external malloced block slim_age_t age_; // nonWF only: the age of the individual, in cycles; -1 in WF models slim_popsize_t index_; // the individual index in that subpop (0-based, and not multiplied by 2) Subpopulation *subpopulation_; // the subpop to which we belong; cannot be a reference because it changes on migration! + // Trait offsets. If the species has 0 traits offsets_for_traits_ is nullptr; if 1 trait it points to + // _offset_for_trait_0_ for memory locality; if 2+ traits it points to an OWNED malloced buffer. + slim_effect_t offset_for_trait_0_; + slim_effect_t *offsets_for_traits_; + // Continuous space ivars. These are effectively free tag values of type float, unless they are used by interactions. double spatial_x_, spatial_y_, spatial_z_; @@ -323,6 +328,7 @@ class Individual : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_containsMutations(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); static EidosValue_SP ExecuteMethod_Accelerated_countOfMutationsOfType(EidosObject **p_values, size_t p_values_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_haplosomesForChromosomes(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_offsetForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_relatedness(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_sharedParentCount(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); static EidosValue_SP ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosObject **p_values, size_t p_values_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); @@ -402,6 +408,7 @@ class Individual_Class : public EidosDictionaryUnretained_Class virtual const std::vector *Methods(void) const override; virtual EidosValue_SP ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const override; + EidosValue_SP ExecuteMethod_setOffsetForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_outputIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_outputIndividualsToVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_readIndividualsFromVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index a15cb71c..83d2c155 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1359,6 +1359,8 @@ const std::string &gStr_countOfMutationsOfType = EidosRegisteredString("countOfM const std::string &gStr_positionsOfMutationsOfType = EidosRegisteredString("positionsOfMutationsOfType", gID_positionsOfMutationsOfType); const std::string &gStr_containsMarkerMutation = EidosRegisteredString("containsMarkerMutation", gID_containsMarkerMutation); const std::string &gStr_haplosomesForChromosomes = EidosRegisteredString("haplosomesForChromosomes", gID_haplosomesForChromosomes); +const std::string &gStr_offsetForTrait = EidosRegisteredString("offsetForTrait", gID_offsetForTrait); +const std::string &gStr_setOffsetForTrait = EidosRegisteredString("setOffsetForTrait", gID_setOffsetForTrait); const std::string &gStr_relatedness = EidosRegisteredString("relatedness", gID_relatedness); const std::string &gStr_sharedParentCount = EidosRegisteredString("sharedParentCount", gID_sharedParentCount); const std::string &gStr_mutationsOfType = EidosRegisteredString("mutationsOfType", gID_mutationsOfType); diff --git a/core/slim_globals.h b/core/slim_globals.h index ca2dc2bb..74a51ad8 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -947,6 +947,8 @@ extern const std::string &gStr_countOfMutationsOfType; extern const std::string &gStr_positionsOfMutationsOfType; extern const std::string &gStr_containsMarkerMutation; extern const std::string &gStr_haplosomesForChromosomes; +extern const std::string &gStr_offsetForTrait; +extern const std::string &gStr_setOffsetForTrait; extern const std::string &gStr_relatedness; extern const std::string &gStr_sharedParentCount; extern const std::string &gStr_mutationsOfType; @@ -1422,6 +1424,8 @@ enum _SLiMGlobalStringID : int { gID_positionsOfMutationsOfType, gID_containsMarkerMutation, gID_haplosomesForChromosomes, + gID_offsetForTrait, + gID_setOffsetForTrait, gID_relatedness, gID_sharedParentCount, gID_mutationsOfType, diff --git a/core/trait.h b/core/trait.h index d4fc06cb..d812082f 100644 --- a/core/trait.h +++ b/core/trait.h @@ -85,6 +85,8 @@ class Trait : public EidosDictionaryRetained inline __attribute__((always_inline)) void SetIndex(int64_t p_index) { index_ = p_index; } // only from AddTrait() inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } + inline slim_effect_t DefaultOffset(void) const { return (type_ == TraitType::kAdditive) ? 0.0 : 1.0; } + // // Eidos support From 39005f6fcf0b46c81eee83a66221d74b1c090911 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 10 Oct 2025 21:18:48 -0400 Subject: [PATCH 009/107] fix build errors --- core/individual.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/individual.cpp b/core/individual.cpp index 427c3b5e..671a0807 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -84,7 +84,7 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu // Set up per-trait information such as phenotype caches and individual offsets Species &species = subpopulation_->species_; - std::vector &traits = species.traits_; + const std::vector &traits = species.Traits(); int trait_count = (int)traits.size(); if (trait_count == 1) @@ -3105,6 +3105,7 @@ EidosValue_SP Individual::ExecuteMethod_haplosomesForChromosomes(EidosGlobalStri // EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { +#pragma unused (p_method_id, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); // get the trait indices, with bounds-checking @@ -4025,6 +4026,7 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ // EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { +#pragma unused (p_method_id, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); EidosValue *offset_value = p_arguments[1].get(); @@ -4052,7 +4054,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin // pattern 1: setting the default offset value for each trait in one or more individuals for (int64_t trait_index : trait_indices) { - Trait *trait = species->traits_[trait_index]; + Trait *trait = species->Traits()[trait_index]; slim_effect_t offset = trait->DefaultOffset(); for (int individual_index = 0; individual_index < individuals_count; ++individual_index) From 24bd765b0b9433194a29a2593f178c8c7d9bcf72 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 12 Oct 2025 18:12:46 -0400 Subject: [PATCH 010/107] add some multitrait unit tests; fix a couple of bugs --- QtSLiM/help/SLiMHelpClasses.html | 4 +- SLiMgui/SLiMHelpClasses.rtf | 15 ++-- VERSIONS | 1 + core/individual.cpp | 70 ++++++++++++------ core/individual.h | 2 + core/slim_test.cpp | 1 + core/slim_test.h | 1 + core/slim_test_genetics.cpp | 122 +++++++++++++++++++++++++++++++ core/species_eidos.cpp | 2 +- core/subpopulation.cpp | 23 +++++- core/subpopulation.h | 4 + core/trait.cpp | 26 +++++++ core/trait.h | 7 +- 13 files changed, 244 insertions(+), 34 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 7d6e7493..6786b790 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -496,9 +496,9 @@

Note that this relatedness is simply pedigree-based relatedness, and does not necessarily correspond to genetic relatedness, because of the effects of factors like assortment and recombination.  If a metric of actual genetic relatedness is desired, tree-sequence recording can be used after simulation is complete, to compute the exact genetic relatedness between individuals based upon the complete ancestry tree (a topic which is beyond the scope of this manual).  Actual genetic relatedness cannot presently be calculated during a simulation run; the information is implicitly contained in the recorded tree-sequence tables, but calculating it is too computationally expensive to be reasonable.

This method assumes that the grandparents (or the parents, if grandparental information is not available) are themselves unrelated and that they are not inbred; this assumption is necessary because we have no information about their parentage, since SLiM’s pedigree tracking information only goes back two generations.  Be aware that in a model where inbreeding or selfing occurs at all (including “incidental selfing”, where a hermaphroditic individual happens to choose itself as a mate), some level of “background relatedness” will be present and this assumption will be violated.  In such circumstances, relatedness() will therefore tend to underestimate the degree of relatedness between individuals, and the greater the degree of inbreeding, the greater the underestimation will be.  If inbreeding is allowed in a model – and particularly if it is common – the results of relatedness() should therefore not be taken as an estimate of absolute relatedness, but can still be useful as an estimate of relative relatedness (indicating that, say, A appears from the information available to be more closely related to B than it is to C).

See also sharedParentCount() for a different metric of relatedness.

-

– (void)setOffsetForTrait([Nio<Trait> trait = NULL], [Nif offset = NULL])

+

+ (void)setOffsetForTrait([Nio<Trait> trait = NULL], [Nif offset = NULL])

Sets the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

-

The parameter offset must follow one of four patterns.  In the first pattern, offset is NULL; this sets the offset for each of the specified traits to its default value (0.0 for additive traits, 1.0 for multiplicative traits) in each target individual.  In the second pattern, offset is a singleton value; this sets the given offset for each of the specified traits in each target individual.  In the third pattern, offset is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual.  In the fourth pattern, offset is of length equal to the number of specified traits times the number of target individuals; this uses offset to provide a different offset value for each trait in each individual, using consecutive values from offset to set the offset for each of the specified traits in one individual before moving to the next individual.

+

The parameter offset must follow one of four patterns.  In the first pattern, offset is NULL; this draws the offset for each of the specified traits from each trait’s individual-offset distribution (defined by each trait’s individualOffsetMean and individualOffsetSD properties) in each target individual.  (Note that individual offsets are automatically drawn from these distributions when an individual is created; this re-draws new offset values.)  In the second pattern, offset is a singleton value; this sets the given offset for each of the specified traits in each target individual.  In the third pattern, offset is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual.  In the fourth pattern, offset is of length equal to the number of specified traits times the number of target individuals; this uses offset to provide a different offset value for each trait in each individual, using consecutive values from offset to set the offset for each of the specified traits in one individual before moving to the next individual.

+ (void)setSpatialPosition(float position)

Sets the spatial position of the individual (as accessed through the spatialPosition property).  The length of position (the number of coordinates in the spatial position of an individual) depends upon the spatial dimensionality declared with initializeSLiMOptions().  If the spatial dimensionality is zero (as it is by default), it is an error to call this method.  The elements of position are set into the values of the x, y, and z properties (if those properties are encompassed by the spatial dimensionality of the simulation).  In other words, if the declared dimensionality is "xy", calling individual.setSpatialPosition(c(1.0, 0.5)) property is equivalent to individual.x = 1.0; individual.y = 0.5; individual.z is not set (even if a third value is supplied in position) since it is not encompassed by the simulation’s dimensionality in this example.

Note that this is an Eidos class method, somewhat unusually, which allows it to work in a special way when called on a vector of individuals.  When the target vector of individuals is non-singleton, this method can do one of two things.  If position contains just a single point (i.e., is equal in length to the spatial dimensionality of the model), the spatial position of all of the target individuals will be set to the given point.  Alternatively, if position contains one point per target individual (i.e., is equal in length to the number of individuals multiplied by the spatial dimensionality of the model), the spatial position of each target individual will be set to the corresponding point from position (where the point data is concatenated, not interleaved, just as it would be returned by accessing the spatialPosition property on the vector of target individuals).  Calling this method with a position vector of any other length is an error.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index b28ccfcb..5e03a22a 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -4171,7 +4171,7 @@ See also \f4\fs20 for a different metric of relatedness.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(void)setOffsetForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif offset = NULL])\ +\f3\fs18 \cf2 +\'a0(void)setOffsetForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0offset\'a0=\'a0NULL])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Sets the individual offset(s) for the trait(s) specified by @@ -4183,15 +4183,16 @@ See also \f4\fs20 objects; \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ -The parameter +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 The parameter \f3\fs18 offset \f4\fs20 must follow one of four patterns. In the first pattern, offset is \f3\fs18 NULL -\f4\fs20 ; this sets the offset for each of the specified traits to its default value ( -\f3\fs18 0.0 -\f4\fs20 for additive traits, -\f3\fs18 1.0 -\f4\fs20 for multiplicative traits) in each target individual. In the second pattern, +\f4\fs20 ; this draws the offset for each of the specified traits from each trait\'92s individual-offset distribution (defined by each trait\'92s +\f3\fs18 individualOffsetMean +\f4\fs20 and +\f3\fs18 individualOffsetSD +\f4\fs20 properties) in each target individual. (Note that individual offsets are automatically drawn from these distributions when an individual is created; this re-draws new offset values.) In the second pattern, \f3\fs18 offset \f4\fs20 is a singleton value; this sets the given offset for each of the specified traits in each target individual. In the third pattern, \f3\fs18 offset diff --git a/VERSIONS b/VERSIONS index e91ab424..205946a4 100644 --- a/VERSIONS +++ b/VERSIONS @@ -55,6 +55,7 @@ multitrait branch: add support in Individual for the individual's offset for each trait add -(float)offsetForTrait([Nio trait = NULL]) add +(void)setOffsetForTrait([Nio trait = NULL], [Nif offset = NULL]) + draw an individual's trait offsets from the trait individual-offset distributions, at the individual's moment of generation version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index 671a0807..0a881e17 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -53,6 +53,7 @@ bool Individual::s_any_individual_fitness_scaling_set_ = false; // individual first, haplosomes later; this is the new multichrom paradigm // BCH 10/12/2024: Note that this will rarely be called after simulation startup; see NewSubpopIndividual() +// BCH 10/12/2025: Note also that NewSubpopIndividual() will rarely be called in WF models; see the Munge...() methods Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age) : #ifdef SLIMGUI color_set_(false), @@ -83,15 +84,57 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu } // Set up per-trait information such as phenotype caches and individual offsets + _DrawTraitOffsets(); + + // Initialize tag values to the "unset" value + tag_value_ = SLIM_TAG_UNSET_VALUE; + tagF_value_ = SLIM_TAGF_UNSET_VALUE; + tagL0_set_ = false; + tagL1_set_ = false; + tagL2_set_ = false; + tagL3_set_ = false; + tagL4_set_ = false; + + // Initialize x/y/z to 0.0, only when leak-checking (they show up as used before initialized in Valgrind) +#if SLIM_LEAK_CHECKING + spatial_x_ = 0.0; + spatial_y_ = 0.0; + spatial_z_ = 0.0; +#endif +} + +void Individual::_DrawTraitOffsets(void) +{ + // Set up per-trait individual-level information such as individual offsets. This is called by + // Individual::Individual(), but also in various other places where individuals are re-used. + + // FIXME MULTITRAIT: this will probably be a pain point; maybe we can skip it if offsets have never been changed by the user? + // I imagine a design where there is a bool flag that says "the offsets for this individual have been initialized". This + // would allow a lazy caching scheme; if an offset is queried or set, all the offsets in that individual are then set up + // and the flag is set to indicate that it has been set up. Every tick, offsets will probably be needed for every individual + // in order to calculate phenotypes, so we can't avoid that work altogether. But we can avoid doing it one individual at a + // time, with a lot of setup overhead here to get the traits, get the RNG, etc.; we could do it in bulk for all individuals + // in a species, at the point when we're calculating everybody's phenotypes, which would allow us to do it very quickly. + // The only difficulty I see with such a lazy caching scheme is: if the trait's individual-offset distribution is *changed* + // by the script, then at that moment, the individual offsets of every alive individual need to be initialized using the old + // distribution before it changes, otherwise they will (incorrectly) draw from the new distribution. So that's a little + // tricky, but doable. I'm going to put off doing this until later, though, so as to not get bogged down. BCH 10/12/2025 + + // FIXME MULTITRAIT: note also that if all trait individual offsets have SD == 0, and thus initialize to a constant, we + // could use a buffer of default trait values that we memcpy() in to each individual's offset buffer. That would be + // a strategy that could easily be used when we bulk-initialize the offsets of all uninitialized individuals, for example. + + // FIXME MULTITRAIT: also, _DrawIndividualOffset() looks up the RNG; when doing this work in bulk, we can look up the RNG + // once and then pass it in to DrawIndividualOffset() instead of having it look it up. Much optimization to do in bulk. + Species &species = subpopulation_->species_; const std::vector &traits = species.Traits(); int trait_count = (int)traits.size(); if (trait_count == 1) { - // FIXME MULTITRAIT: DefaultOffset() has a branch; maybe better for each trait to have a cached slim_effect_t for it? or maybe not? offsets_for_traits_ = &offset_for_trait_0_; - offset_for_trait_0_ = traits[0]->DefaultOffset(); + offset_for_trait_0_ = traits[0]->DrawIndividualOffset(); } else if (trait_count == 0) { @@ -99,28 +142,11 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu } else { - // FIXME MULTITRAIT: we could keep a buffer of default trait values in the Species, and just memcpy() here offsets_for_traits_ = static_cast(malloc(trait_count * sizeof(slim_effect_t))); for (int trait_index = 0; trait_index < trait_count; ++trait_index) - offsets_for_traits_[trait_index] = traits[trait_index]->DefaultOffset(); + offsets_for_traits_[trait_index] = traits[trait_index]->DrawIndividualOffset(); } - - // Initialize tag values to the "unset" value - tag_value_ = SLIM_TAG_UNSET_VALUE; - tagF_value_ = SLIM_TAGF_UNSET_VALUE; - tagL0_set_ = false; - tagL1_set_ = false; - tagL2_set_ = false; - tagL3_set_ = false; - tagL4_set_ = false; - - // Initialize x/y/z to 0.0, only when leak-checking (they show up as used before initialized in Valgrind) -#if SLIM_LEAK_CHECKING - spatial_x_ = 0.0; - spatial_y_ = 0.0; - spatial_z_ = 0.0; -#endif } Individual::~Individual(void) @@ -4051,11 +4077,11 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin if (offset_value->Type() == EidosValueType::kValueNULL) { - // pattern 1: setting the default offset value for each trait in one or more individuals + // pattern 1: drawing a default offset value for each trait in one or more individuals for (int64_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; - slim_effect_t offset = trait->DefaultOffset(); + slim_effect_t offset = trait->DrawIndividualOffset(); for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { diff --git a/core/individual.h b/core/individual.h index 7eb7462c..980fe0f0 100644 --- a/core/individual.h +++ b/core/individual.h @@ -169,6 +169,8 @@ class Individual : public EidosDictionaryUnretained Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age); virtual ~Individual(void) override; + void _DrawTraitOffsets(void); + inline __attribute__((always_inline)) void ClearColor(void) { #ifdef SLIMGUI // BCH 3/23/2025: color variables now only exist in SLiMgui, to save on memory footprint diff --git a/core/slim_test.cpp b/core/slim_test.cpp index 543d4379..70dbb94d 100644 --- a/core/slim_test.cpp +++ b/core/slim_test.cpp @@ -461,6 +461,7 @@ int RunSLiMTests(void) _RunChromosomeTests(); _RunMutationTests(); _RunHaplosomeTests(temp_path); + _RunMultitraitTests(); _RunSubpopulationTests(); _RunIndividualTests(); _RunSubstitutionTests(); diff --git a/core/slim_test.h b/core/slim_test.h index 248e787e..fb9901e8 100644 --- a/core/slim_test.h +++ b/core/slim_test.h @@ -49,6 +49,7 @@ extern void _RunGenomicElementTests(void); extern void _RunChromosomeTests(void); extern void _RunMutationTests(void); extern void _RunHaplosomeTests(const std::string &temp_path); +extern void _RunMultitraitTests(void); extern void _RunSubpopulationTests(void); extern void _RunIndividualTests(void); extern void _RunErrorPositionTests(void); diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 35a6fc50..ec73bb3e 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -941,6 +941,128 @@ void _RunHaplosomeTests(const std::string &temp_path) } } +#pragma mark Multitrait tests +void _RunMultitraitTests(void) +{ + // two-trait base model implemented in WF and nonWF -- one trait multiplicative, one trait additive + const std::string mt_base_p1_WF = +R"V0G0N( +initialize() { + defineConstant("T_height", initializeTrait("height", "multiplicative", 2.0)); + defineConstant("T_weight", initializeTrait("weight", "additive", 186.0)); + initializeMutationRate(1e-7); + initializeMutationType("m1", 0.5, "f", 0.0); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 99999); + initializeRecombinationRate(1e-8); +} +1 late() { sim.addSubpop("p1", 5); } +5 late() { } +)V0G0N"; + + const std::string mt_base_p1_nonWF = +R"V0G0N( +initialize() { + initializeSLiMModelType("nonWF"); + defineConstant("T_height", initializeTrait("height", "multiplicative", 2.0)); + defineConstant("T_weight", initializeTrait("weight", "additive", 186.0)); + initializeMutationRate(1e-7); + initializeMutationType("m1", 0.5, "f", 0.0); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 99999); + initializeRecombinationRate(1e-8); +} +reproduction() { p1.addCrossed(individual, p1.sampleIndividuals(1)); } +1 late() { sim.addSubpop("p1", 5); } +late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } +5 late() { } +)V0G0N"; + + for (int model = 0; model <= 1; ++model) + { + std::string mt_base_p1 = ((model == 0) ? mt_base_p1_WF : mt_base_p1_nonWF); + + SLiMAssertScriptSuccess(mt_base_p1); + + // trait defines, trait lookup + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(c(T_height, T_weight), sim.traits)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(c(T_height, T_weight), community.allTraits)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height, sim.traitsWithIndices(0))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight, sim.traitsWithIndices(1))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(c(T_height, T_weight), sim.traitsWithIndices(0:1))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(c(T_weight, T_height), sim.traitsWithIndices(1:0))) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.traitsWithIndices(2); }", "out-of-range index (2)", __LINE__); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height, sim.traitsWithNames('height'))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight, sim.traitsWithNames('weight'))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(c(T_height, T_weight), sim.traitsWithNames(c('height', 'weight')))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(c(T_weight, T_height), sim.traitsWithNames(c('weight', 'height')))) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.traitsWithNames('typo'); }", "trait with the given name (typo)", __LINE__); + + // basic trait properties + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.baselineOffset, 2.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.baselineOffset, 186.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.baselineOffset = 12.5; if (!identical(T_height.baselineOffset, 12.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.baselineOffset = 17.25; if (!identical(T_weight.baselineOffset, 17.25)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.directFitnessEffect, F)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.directFitnessEffect, F)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.directFitnessEffect = T; if (!identical(T_height.directFitnessEffect, T)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.directFitnessEffect = T; if (!identical(T_weight.directFitnessEffect, T)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.index, 0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.index, 1)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.individualOffsetMean, 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.individualOffsetMean, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; if (!identical(T_height.individualOffsetMean, 3.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; if (!identical(T_weight.individualOffsetMean, 2.5)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.individualOffsetSD, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.individualOffsetSD, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetSD = 3.5; if (!identical(T_height.individualOffsetSD, 3.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetSD = 2.5; if (!identical(T_weight.individualOffsetSD, 2.5)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.name, 'height')) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.name, 'weight')) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.species, sim)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.species, sim)) stop(); }"); + + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { if (!identical(T_height.tag, 12)) stop(); }", "before being set", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { if (!identical(T_weight.tag, 3)) stop(); }", "before being set", __LINE__); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.tag = 12; if (!identical(T_height.tag, 12)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.tag = 3; if (!identical(T_weight.tag, 3)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.type, 'multiplicative')) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.type, 'additive')) stop(); }"); + + // individual offset + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.offsetForTrait(T_height), rep(1.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.offsetForTrait(T_weight), rep(0.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.offsetForTrait(NULL), rep(c(1.0, 0.0), 5))) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, 3); p1.individuals.setOffsetForTrait(1, 4.5); if (!identical(p1.individuals.offsetForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, 1:5 * 2 - 1); p1.individuals.setOffsetForTrait(1, 1:5 * 2); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(NULL, 1:10); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(NULL, 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(0,1), 1:10); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(0,1), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_height), rep(3.5, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_weight), rep(2.5, 5))) stop(); }"); + + // individual trait property access (not yet fully implemented) + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.height; }", "trait height cannot be accessed (FIXME MULTITRAIT)", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.weight; }", "trait weight cannot be accessed (FIXME MULTITRAIT)", __LINE__); + } + + std::cout << "_RunMultitraitTests() done" << std::endl; +} + diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index a344b12c..36689e8a 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1667,7 +1667,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string double individualOffsetSD; if (individualOffsetSD_value->Type() == EidosValueType::kValueNULL) - individualOffsetSD = (type == TraitType::kMultiplicative) ? 1.0 : 0.0; + individualOffsetSD = 0.0; else individualOffsetSD = individualOffsetSD_value->FloatAtIndex_NOCAST(0, nullptr); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index bb0a5bf0..30fcab15 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -131,7 +131,7 @@ void Subpopulation::WipeIndividualsAndHaplosomes(std::vector &p_in bool is_female = (index < p_first_male); individual->sex_ = (is_female ? IndividualSex::kFemale : IndividualSex::kMale); - + for (Chromosome *chromosome : chromosomes) { // Determine what kind of haplosomes to make for this chromosome @@ -4500,6 +4500,9 @@ bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigree if (f_spatial) individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent1); + // Draw new individual trait offsets from each trait's individual-offset distribution + individual->_DrawTraitOffsets(); + // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; int currentHaplosomeIndex = 0; @@ -4885,6 +4888,9 @@ bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pe if (f_spatial) individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent1); + // Draw new individual trait offsets from each trait's individual-offset distribution + individual->_DrawTraitOffsets(); + // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; const int currentHaplosomeIndex = 0; @@ -4968,6 +4974,9 @@ bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pe if (f_spatial) individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent1); + // Draw new individual trait offsets from each trait's individual-offset distribution + individual->_DrawTraitOffsets(); + // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; const int currentHaplosomeIndex = 0; @@ -5048,6 +5057,9 @@ bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreei if (f_spatial) individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); + // Draw new individual trait offsets from each trait's individual-offset distribution + individual->_DrawTraitOffsets(); + // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; int currentHaplosomeIndex = 0; @@ -5247,6 +5259,9 @@ bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreei if (f_spatial) individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); + // Draw new individual trait offsets from each trait's individual-offset distribution + individual->_DrawTraitOffsets(); + // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; int currentHaplosomeIndex = 0; @@ -5520,6 +5535,9 @@ bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_ped if (f_spatial) individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); + // Draw new individual trait offsets from each trait's individual-offset distribution + individual->_DrawTraitOffsets(); + // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; const int currentHaplosomeIndex = 0; @@ -5603,6 +5621,9 @@ bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_ped if (f_spatial) individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); + // Draw new individual trait offsets from each trait's individual-offset distribution + individual->_DrawTraitOffsets(); + // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; const int currentHaplosomeIndex = 0; diff --git a/core/subpopulation.h b/core/subpopulation.h index f481800f..e48c996d 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -292,6 +292,10 @@ class Subpopulation : public EidosDictionaryUnretained back->age_ = p_age; back->index_ = p_individual_index; back->subpopulation_ = this; + + // Draw new individual trait offsets from each trait's individual-offset distribution + back->_DrawTraitOffsets(); + return back; } diff --git a/core/trait.cpp b/core/trait.cpp index bc312bfb..37d9eecb 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -16,6 +16,21 @@ Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, do individualOffsetMean_(p_individualOffsetMean), individualOffsetSD_(p_individualOffsetSD), directFitnessEffect_(p_directFitnessEffect), community_(p_species.community_), species_(p_species) { + _RecacheIndividualOffsetDistribution(); +} + +void Trait::_RecacheIndividualOffsetDistribution(void) +{ + // cache for the fast case of an individual-offset SD of 0.0 + if (individualOffsetSD_ == 0.0) + { + individualOffsetFixed_ = true; + individualOffsetFixedValue_ = static_cast(individualOffsetMean_); + } + else + { + individualOffsetFixed_ = false; + } } Trait::~Trait(void) @@ -33,6 +48,15 @@ void Trait::Print(std::ostream &p_ostream) const p_ostream << Class()->ClassNameForDisplay() << "<" << name_ << ">"; } +slim_effect_t Trait::_DrawIndividualOffset(void) const +{ + // draws from a normal distribution defined by individualOffsetMean_ and individualOffsetSD_ + // note the individualOffsetSD_ == 0 case was already handled by DrawIndividualOffset() + gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); + + return static_cast(gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_); +} + EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) { // All of our strings are in the global registry, so we can require a successful lookup @@ -136,6 +160,7 @@ void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_v EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property individualOffsetMean requires a finite value (not NAN or INF)." << EidosTerminate(); individualOffsetMean_ = value; + _RecacheIndividualOffsetDistribution(); return; } case gID_individualOffsetSD: @@ -146,6 +171,7 @@ void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_v EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property individualOffsetSD requires a finite value (not NAN or INF)." << EidosTerminate(); individualOffsetSD_ = value; + _RecacheIndividualOffsetDistribution(); return; } case gID_tag: diff --git a/core/trait.h b/core/trait.h index d812082f..5d033a9a 100644 --- a/core/trait.h +++ b/core/trait.h @@ -59,6 +59,9 @@ class Trait : public EidosDictionaryRetained // offsets double baselineOffset_; + + bool individualOffsetFixed_; // true if individualOffsetSD_ == 0.0 + slim_effect_t individualOffsetFixedValue_; // equal to individualOffsetMean_ if individualOffsetFixed_ == true; pre-cast for speed double individualOffsetMean_; double individualOffsetSD_; @@ -85,7 +88,9 @@ class Trait : public EidosDictionaryRetained inline __attribute__((always_inline)) void SetIndex(int64_t p_index) { index_ = p_index; } // only from AddTrait() inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } - inline slim_effect_t DefaultOffset(void) const { return (type_ == TraitType::kAdditive) ? 0.0 : 1.0; } + void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetFixed_ and individualOffsetFixedValue_ + slim_effect_t _DrawIndividualOffset(void) const; // draws from a normal distribution defined by individualOffsetMean_ and individualOffsetSD_ + inline slim_effect_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } // From 72e3533c559ee8da601f899d4d14112b359e4b02 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 12 Oct 2025 18:32:04 -0400 Subject: [PATCH 011/107] clean up the GetProperty_NO_SIGNATURE() logic a bit --- eidos/eidos_value.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/eidos/eidos_value.cpp b/eidos/eidos_value.cpp index bbf41b4b..659b3fc0 100644 --- a/eidos/eidos_value.cpp +++ b/eidos/eidos_value.cpp @@ -2176,11 +2176,10 @@ EidosValue_SP EidosValue_Object::GetPropertyOfElements(EidosGlobalStringID p_pro // goes through a special vectorized method, not through GetProperty()! if (!signature) { + // Note that in this NO_SIGNATURE code path we don't check for a zero-length target and return a zero-length + // result; since we have no signature, we have no way to know what type that result should be. The class + // implementation of GetProperty_NO_SIGNATURE() will handle the zero-length case. const EidosClass *target_class = class_; - - if (values_size == 0) - EIDOS_TERMINATION << "ERROR (EidosValue_Object::GetPropertyOfElements): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " does not specify an unambiguous value type, and thus cannot be accessed on a zero-length vector." << EidosTerminate(nullptr); - EidosValue_SP result = target_class->GetProperty_NO_SIGNATURE(p_property_id, values_, values_size); // Access of singleton properties retains the matrix/array structure of the target From 58d411c48568a17404299a2d0ceadea809fe25b8 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 13 Oct 2025 12:18:35 -0400 Subject: [PATCH 012/107] add Species properties for each trait in the species --- QtSLiM/help/SLiMHelpFunctions.html | 4 +- SLiMgui/SLiMHelpFunctions.rtf | 50 +++++++++++++----- VERSIONS | 4 +- core/individual.cpp | 73 +++++++++++++------------- core/individual.h | 4 -- core/slim_test.cpp | 75 ++++++++------------------- core/slim_test_genetics.cpp | 27 ++++++++++ core/species.cpp | 14 +---- core/species.h | 14 ++++- core/species_eidos.cpp | 82 ++++++++++++++++++++++++++---- eidos/eidos_class_Object.cpp | 51 +++++++++++++------ eidos/eidos_class_Object.h | 6 +-- eidos/eidos_functions_other.cpp | 72 +------------------------- eidos/eidos_property_signature.h | 11 +++- eidos/eidos_script.cpp | 68 +++++++++++++++++++++++++ eidos/eidos_script.h | 2 + eidos/eidos_value.cpp | 42 ++------------- 17 files changed, 335 insertions(+), 264 deletions(-) diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 3fbb24ba..6efd6ee0 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -147,8 +147,8 @@

The color parameter, if not "", sets a string color value used to represent the species in SLiMgui.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB" (see the Eidos manual for details).  If color is the empty string, "", SLiMgui will choose a default color.

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [logical$ directFitnessEffect = F])

Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

-

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not conflict with any other trait in any species in the model, it must not conflict with any global variable or constant, and it must not conflict with the name of any existing property on the Individual class.  The second requirement is because, after the new trait is created, a new global constant is defined that represents the trait’s index within the species, for quick reference to the trait in various contexts.  The third requirement is because, after the new trait is created, a new property is added to individuals of the species, with the same name as the new trait, that allows the trait values of individuals to be accessed directly as properties.  For example, if the new trait is named myTraitT, a new global constant myTraitT would be defined as myTraitT’s index in the species, and access to an individual’s trait value would be possible through the property individual.myTraitT.  It is suggested, but not required, that trait names should end with a capital T.

-

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).

+

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the Individual or Species classes.  These requirements are necessary because, after the new trait is created, new properties added to the Individual and Species classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named heightT, getting and setting an individual’s trait value would be possible through the property individual.heightT.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.heightT would provide the Trait object named heightT.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("heightT", heightT) would allow the Trait object to be referenced simply as heightT.  For clarity, it is suggested that trait names end in a "T" to differentiate them from other variables and properties, but this is not required.

+

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).  The shorter versions "mul" and "add" are also allowed.

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive traits, such that the baseline offset has no effect upon the trait value.

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  As for the baseline offset, the individual offset mean defaults (if NULL is passed) to 1.0 for multiplicative traits, 0.0 for additive traits, to produce no effect.  The default standard deviation for the individual offset, if NULL is passed, is 0.0.  If NULL is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.

Finally, the directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index f2d4549f..b5932a02 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -1480,19 +1480,39 @@ The \f2\fs20 class documentation.\ The \f1\fs18 name -\f2\fs20 parameter gives the name of the new trait. This may be any (non-empty) string, except that it must not conflict with any other trait in any species in the model, it must not conflict with any global variable or constant, and it must not conflict with the name of any existing property on the +\f2\fs20 parameter gives the name of the new trait. This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the \f1\fs18 Individual -\f2\fs20 class. The second requirement is because, after the new trait is created, a new global constant is defined that represents the trait\'92s index within the species, for quick reference to the trait in various contexts. The third requirement is because, after the new trait is created, a new property is added to individuals of the species, with the same name as the new trait, that allows the trait values of individuals to be accessed directly as properties. For example, if the new trait is named -\f1\fs18 myTraitT -\f2\fs20 , a new global constant -\f1\fs18 myTraitT -\f2\fs20 would be defined as -\f1\fs18 myTraitT -\f2\fs20 \'92s index in the species, and access to an individual\'92s trait value would be possible through the property -\f1\fs18 individual.myTraitT -\f2\fs20 . It is suggested, but not required, that trait names should end with a capital -\f1\fs18 T -\f2\fs20 .\ +\f2\fs20 or +\f1\fs18 Species +\f2\fs20 classes. These requirements are necessary because, after the new trait is created, new properties added to the +\f1\fs18 Individual +\f2\fs20 and +\f1\fs18 Species +\f2\fs20 classes, with the same name as the new trait, for convenience. The new +\f1\fs18 Individual +\f2\fs20 property allows trait values to be accessed directly through a property; for example, if the new trait is named +\f1\fs18 heightT +\f2\fs20 , getting and setting an individual\'92s trait value would be possible through the property +\f1\fs18 individual.heightT +\f2\fs20 . The new +\f1\fs18 Species +\f2\fs20 property allows traits themselves to be accessed directly through a property; continuing the previous example, +\f1\fs18 sim.heightT +\f2\fs20 would provide the +\f1\fs18 Trait +\f2\fs20 object named +\f1\fs18 heightT +\f2\fs20 . If desired, +\f1\fs18 defineConstant() +\f2\fs20 may also be used to set up a global constant for a trait; for example, +\f1\fs18 defineConstant("heightT", heightT) +\f2\fs20 would allow the +\f1\fs18 Trait +\f2\fs20 object to be referenced simply as +\f1\fs18 heightT +\f2\fs20 . For clarity, it is suggested that trait names end in a +\f1\fs18 "T" +\f2\fs20 to differentiate them from other variables and properties, but this is not required.\ The \f1\fs18 type \f2\fs20 parameter gives the type of trait to be created, as a @@ -1501,7 +1521,11 @@ The \f1\fs18 "multiplicative" \f2\fs20 , if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or \f1\fs18 "additive" -\f2\fs20 , if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).\ +\f2\fs20 , if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model). The shorter versions +\f1\fs18 "mul" +\f2\fs20 and +\f1\fs18 "add" +\f2\fs20 are also allowed.\ The \f1\fs18 baselineOffset \f2\fs20 parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual. If diff --git a/VERSIONS b/VERSIONS index 205946a4..6586dc4e 100644 --- a/VERSIONS +++ b/VERSIONS @@ -50,12 +50,12 @@ multitrait branch: add MutationType method setDefaultDominanceForTrait(Nio trait, float dominance) (approximately replacing writing into the dominanceCoeff property, but this should not autofix) transition MutationType's internals to keep a separate DE for each trait using a new EffectDistributionInfo struct added C++ IsPureNeutralDFE() method to represent whether all of the effects of a given mutation type are all neutral - add Individual properties for each trait in the individual's species, allowing direct access - this was done by adding GetProperty_NO_SIGNATURE() / SetProperty_NO_SIGNATURE() methods called by EidosValue_Object::GetPropertyOfElements() and EidosValue_Object::SetPropertyOfElements() to support properties with no signature add support in Individual for the individual's offset for each trait add -(float)offsetForTrait([Nio trait = NULL]) add +(void)setOffsetForTrait([Nio trait = NULL], [Nif offset = NULL]) draw an individual's trait offsets from the trait individual-offset distributions, at the individual's moment of generation + add Individual properties for each trait in the individual's species, allowing direct access to the phenotype for each trait in an individual + add Species properties for each trait in the species, allowing direct access to traits in this species version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index 0a881e17..616d6b6e 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -1719,7 +1719,28 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: + { + // Here we implement a special behavior: you can do individual.traitName to access a trait value directly. + Species &species = subpopulation_->species_; + Trait *trait = species.TraitFromStringID(p_property_id); + + if (trait) + { + // We got a hit, but don't know what to do with it for now. When this is hooked up to the trait value, + // I should add an accelerated getter since vectorized access for these trait properties will be very common. + // That will require modifying the accelerated getter mechanism to pass the property id in to the method, though. + // It would do: + //Species *species = Community::SpeciesForIndividualsVector(individuals, (int)p_targets_size); + //Trait *trait = species->TraitFromStringID(p_property_id); + // If the individuals don't belong to a single species, it would look up the species for each individual to get the right trait, I guess, + // since two species could have traits with the same name but at different trait indices, so you have to look up the right index. + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): trait " << trait->Name() << " cannot be accessed (FIXME MULTITRAIT)." << EidosTerminate(); + // we want something like this + //return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_value)); + } + return super::GetProperty(p_property_id); + } } } @@ -2547,7 +2568,21 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue // all others, including gID_none default: + { + // Here we implement a special behavior: you can do individual.traitName to access a trait value directly. + Species &species = subpopulation_->species_; + Trait *trait = species.TraitFromStringID(p_property_id); + + if (trait) + { + // We got a hit, but don't know what to do with it for now. + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): trait " << trait->Name() << " cannot be accessed (FIXME MULTITRAIT)." << EidosTerminate(); + // we want something like this + //trait_value = p_value.FloatAtIndex_NOCAST(0, nullptr); + } + return super::SetProperty(p_property_id, p_value); + } } } @@ -5409,44 +5444,6 @@ EidosValue_SP Individual_Class::ExecuteMethod_setSpatialPosition(EidosGlobalStri return gStaticEidosValueVOID; } -// In these methods we implement a special behavior: you can do individual.traitName to -// access the value for a trait. We do a dynamic lookup from the trait name here. - -EidosValue_SP Individual_Class::GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size) const -{ - const Individual *const *individuals = (const Individual *const *)p_targets; - - Species *species = Community::SpeciesForIndividualsVector(individuals, (int)p_targets_size); - Trait *trait = species->TraitFromStringID(p_property_id); - - if (trait) - { - // We got a hit, but don't know what to do with it for now - EIDOS_TERMINATION << "ERROR (Individual_Class::GetProperty_NO_SIGNATURE): trait " << trait->Name() << " cannot be accessed (FIXME MULTITRAIT)." << EidosTerminate(); - } - - return super::GetProperty_NO_SIGNATURE(p_property_id, p_targets, p_targets_size); -} - -void Individual_Class::SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size, const EidosValue &p_value) const -{ - const Individual *const *individuals = (const Individual *const *)p_targets; - - Species *species = Community::SpeciesForIndividualsVector(individuals, (int)p_targets_size); - Trait *trait = species->TraitFromStringID(p_property_id); - - if (trait) - { - // Eidos did not type-check for us, because there is no signature! We have to check it ourselves. - if (p_value.Type() != EidosValueType::kValueFloat) - EIDOS_TERMINATION << "ERROR (Individual_Class::SetProperty_NO_SIGNATURE): assigned value must be of type float for trait-value property " << trait->Name() << "." << EidosTerminate(); - - // We got a hit, but don't know what to do with it for now - EIDOS_TERMINATION << "ERROR (Individual_Class::GetProperty_NO_SIGNATURE): trait " << trait->Name() << " cannot be accessed (FIXME MULTITRAIT)." << EidosTerminate(); - } - - return super::SetProperty_NO_SIGNATURE(p_property_id, p_targets, p_targets_size, p_value); -} diff --git a/core/individual.h b/core/individual.h index 980fe0f0..9c078626 100644 --- a/core/individual.h +++ b/core/individual.h @@ -415,10 +415,6 @@ class Individual_Class : public EidosDictionaryUnretained_Class EidosValue_SP ExecuteMethod_outputIndividualsToVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_readIndividualsFromVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_setSpatialPosition(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; - - // These special accessors are implemented to handle the properties on Individual for defined traits - virtual EidosValue_SP GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size) const override; - virtual void SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size, const EidosValue &p_value) const override; }; diff --git a/core/slim_test.cpp b/core/slim_test.cpp index 70dbb94d..f12f7e7d 100644 --- a/core/slim_test.cpp +++ b/core/slim_test.cpp @@ -42,6 +42,23 @@ static int gSLiMTestSuccessCount = 0; static int gSLiMTestFailureCount = 0; +static void _SLiMTestCleanup(Community *community) +{ + if (community) + for (Species *species : community->AllSpecies()) + species->DeleteAllMutationRuns(); + + delete community; + InteractionType::DeleteSparseVectorFreeList(); + + ClearErrorContext(); + + if (gEidos_DictionaryNonRetainReleaseReferenceCounter > 0) + std::cerr << "WARNING (SLiMAssertScriptSuccess): gEidos_DictionaryNonRetainReleaseReferenceCounter == " << gEidos_DictionaryNonRetainReleaseReferenceCounter << " at end of test!" << std::endl; + + gEidos_DictionaryNonRetainReleaseReferenceCounter = 0; +} + // Instantiates and runs the script, and prints an error if the result does not match expectations void SLiMAssertScriptSuccess(const std::string &p_script_string, int p_lineNumber) { @@ -89,25 +106,13 @@ void SLiMAssertScriptSuccess(const std::string &p_script_string, int p_lineNumbe return; } - if (community) - for (Species *species : community->AllSpecies()) - species->DeleteAllMutationRuns(); - - delete community; - InteractionType::DeleteSparseVectorFreeList(); - gSLiMTestFailureCount--; // correct for our assumption of failure above gSLiMTestSuccessCount++; //std::cerr << p_script_string << " : " << EIDOS_OUTPUT_SUCCESS_TAG << endl; - ClearErrorContext(); - - if (gEidos_DictionaryNonRetainReleaseReferenceCounter > 0) - std::cerr << "WARNING (SLiMAssertScriptSuccess): gEidos_DictionaryNonRetainReleaseReferenceCounter == " << gEidos_DictionaryNonRetainReleaseReferenceCounter << " at end of test!" << std::endl; - } - - gEidos_DictionaryNonRetainReleaseReferenceCounter = 0; + _SLiMTestCleanup(community); + } } void SLiMAssertScriptRaise(const std::string &p_script_string, const std::string &p_reason_snip, int p_lineNumber, bool p_expect_error_position, bool p_error_is_in_stop) @@ -203,20 +208,8 @@ void SLiMAssertScriptRaise(const std::string &p_script_string, const std::string } } - if (community) - for (Species *species : community->AllSpecies()) - species->DeleteAllMutationRuns(); - - delete community; - InteractionType::DeleteSparseVectorFreeList(); - - ClearErrorContext(); - - if (gEidos_DictionaryNonRetainReleaseReferenceCounter > 0) - std::cerr << "WARNING (SLiMAssertScriptRaise): gEidos_DictionaryNonRetainReleaseReferenceCounter == " << gEidos_DictionaryNonRetainReleaseReferenceCounter << " at end of test!" << std::endl; + _SLiMTestCleanup(community); } - - gEidos_DictionaryNonRetainReleaseReferenceCounter = 0; } void SLiMAssertScriptStop(const std::string &p_script_string, int p_lineNumber) @@ -272,20 +265,8 @@ void SLiMAssertScriptStop(const std::string &p_script_string, int p_lineNumber) } } - if (community) - for (Species *species : community->AllSpecies()) - species->DeleteAllMutationRuns(); - - delete community; - InteractionType::DeleteSparseVectorFreeList(); - - ClearErrorContext(); - - if (gEidos_DictionaryNonRetainReleaseReferenceCounter > 0) - std::cerr << "WARNING (SLiMAssertScriptStop): gEidos_DictionaryNonRetainReleaseReferenceCounter == " << gEidos_DictionaryNonRetainReleaseReferenceCounter << " at end of test!" << std::endl; + _SLiMTestCleanup(community); } - - gEidos_DictionaryNonRetainReleaseReferenceCounter = 0; } void SLiMAssertScriptRaisePosition(const std::string &p_script_string, const int p_bad_position, const char *p_reason_snip, int p_lineNumber) @@ -365,20 +346,8 @@ void SLiMAssertScriptRaisePosition(const std::string &p_script_string, const int } } - if (community) - for (Species *species : community->AllSpecies()) - species->DeleteAllMutationRuns(); - - delete community; - InteractionType::DeleteSparseVectorFreeList(); - - ClearErrorContext(); - - if (gEidos_DictionaryNonRetainReleaseReferenceCounter > 0) - std::cerr << "WARNING (SLiMAssertScriptRaise): gEidos_DictionaryNonRetainReleaseReferenceCounter == " << gEidos_DictionaryNonRetainReleaseReferenceCounter << " at end of test!" << std::endl; + _SLiMTestCleanup(community); } - - gEidos_DictionaryNonRetainReleaseReferenceCounter = 0; } diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index ec73bb3e..9697b81d 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -984,6 +984,25 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1); + // initializeTrait() requirements + SLiMAssertScriptRaise("initialize() { initializeTrait('', 'multiplicative', 2.0); }", "non-empty string", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('human height', 'multiplicative', 2.0); }", "valid Eidos identifier", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('migrant', 'multiplicative', 2.0); }", "existing property on Individual", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('avatar', 'multiplicative', 2.0); }", "existing property on Species", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', 2.0); initializeTrait('height', 'multiplicative', 2.0); }", "already a trait in this species", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multi', 2.0); }", "requires type to be either", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', baselineOffset=INF); }", "baselineOffset to be a finite value", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', baselineOffset=NAN); }", "baselineOffset to be a finite value", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=INF, individualOffsetSD=0.0); }", "individualOffsetMean to be a finite value", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=NAN, individualOffsetSD=0.0); }", "individualOffsetMean to be a finite value", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=1.0, individualOffsetSD=INF); }", "individualOffsetSD to be a finite value", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=1.0, individualOffsetSD=NAN); }", "individualOffsetSD to be a finite value", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=2.0, individualOffsetSD=NULL); }", "individual offset parameters be", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=NULL, individualOffsetSD=2.0); }", "individual offset parameters be", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0); initializeTrait('height', 'multiplicative'); }", "already been implicitly defined", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative'); initializeMutationType('m1', 0.5, 'f', 0.0); initializeTrait('weight', 'multiplicative'); }", "before a mutation type is created", __LINE__); + SLiMAssertScriptRaise("initialize() { for (i in 1:257) initializeTrait('height' + i, 'multiplicative'); }", "maximum number of traits", __LINE__); + // trait defines, trait lookup SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(c(T_height, T_weight), sim.traits)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(c(T_height, T_weight), community.allTraits)) stop(); }"); @@ -1055,9 +1074,17 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_height), rep(3.5, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_weight), rep(2.5, 5))) stop(); }"); + // species trait property access + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(sim.height, sim.traitsWithNames('height'))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(sim.weight, sim.traitsWithNames('weight'))) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.height = sim.traitsWithNames('height'); }", "new value for read-only property", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.weight = sim.traitsWithNames('weight'); }", "new value for read-only property", __LINE__); + // individual trait property access (not yet fully implemented) SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.height; }", "trait height cannot be accessed (FIXME MULTITRAIT)", __LINE__); SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.weight; }", "trait weight cannot be accessed (FIXME MULTITRAIT)", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.height = 10.0; }", "trait height cannot be accessed (FIXME MULTITRAIT)", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.weight = 10.0; }", "trait weight cannot be accessed (FIXME MULTITRAIT)", __LINE__); } std::cout << "_RunMultitraitTests() done" << std::endl; diff --git a/core/species.cpp b/core/species.cpp index 3a5060c6..b427c491 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -514,7 +514,7 @@ void Species::GetChromosomeIndicesFromEidosValue(std::vector &Traits(void) { return traits_; } inline __attribute__((always_inline)) int64_t TraitCount(void) { return (int64_t)traits_.size(); } - Trait *TraitFromName(const std::string &p_name); - Trait *TraitFromStringID(EidosGlobalStringID p_string_id); + Trait *TraitFromName(const std::string &p_name) const; + inline __attribute__((always_inline)) Trait *TraitFromStringID(EidosGlobalStringID p_string_id) const + { + // This is used for (hopefully) very fast lookup of a trait based on a string id in Eidos, + // so that the user can do "individual.trait" and get a trait value like a property access + auto iter = trait_from_string_id.find(p_string_id); + + if (iter == trait_from_string_id.end()) + return nullptr; + + return (*iter).second; + } void MakeImplicitTrait(void); void AddTrait(Trait *p_trait); // takes over a retain count from the caller diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 36689e8a..bfe90031 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1598,6 +1598,11 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): (internal error) initializeTrait() was called with an implicitly defined trait. However, the cause of this cannot be diagnosed, indicating an internal logic error." << EidosTerminate(); } + else + { + if (num_mutation_type_inits_ > 0) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() cannot be called after initializeMutationType() has been called; all traits in the species must be defined before a mutation type is created." << EidosTerminate(); + } if (traits_.size() >= SLIM_MAX_TRAITS) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() cannot make a new trait because the maximum number of traits allowed per species (" << SLIM_MAX_TRAITS << ") has already been reached." << EidosTerminate(); @@ -1612,19 +1617,16 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string // name std::string name = name_value->StringAtIndex_NOCAST(0, nullptr); - bool name_problem = (name.length() == 0); - const std::vector *individual_properties = gSLiM_Individual_Class->Properties(); - for (Trait *trait : traits_) - if (trait->Name() == name) - name_problem = true; + if (name.length() == 0) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name be a non-empty string." << EidosTerminate(); - for (EidosPropertySignature_CSP property_signature : *individual_properties) - if (property_signature->property_name_ == name) - name_problem = true; + if (!EidosScript::Eidos_IsIdentifier(name)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name is a valid Eidos identifier." << EidosTerminate(nullptr); - if (name_problem) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires a non-zero-length name that is unique within the species and does not conflict with any Individual property name." << EidosTerminate(); + for (Trait *trait : traits_) + if (trait->Name() == name) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name is unique within the species; there is already a trait in this species with the name '" << name << "'." << EidosTerminate(); // type std::string type_string = type_value->StringAtIndex_NOCAST(0, nullptr); @@ -1685,6 +1687,58 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string AddTrait(trait); num_trait_inits_++; + // Register new property signatures based upon the trait. Note that these signatures are added at the + // class level, so they will be visible on objects regardless of which species they belong to; if + // accessed on an object of the wrong species, however, they will raise a "property not found" error. + // More than one species can have a trait with the same name; in that case, the signature will be + // registered only once, but the objects in more than one species will respond to it. More oddly, in + // SLiMgui the traits from one model will show up in a different model running at the same time, and + // registered trait properties will not go away when you recycle. I'm ok with that. + EidosGlobalStringID trait_stringID = EidosStringRegistry::GlobalStringIDForString(name); + + { + // add a Species property that returns the trait object + const EidosPropertySignature *existing_signature = gSLiM_Species_Class->SignatureForProperty(trait_stringID); + + if (existing_signature) + { + // an existing signature must return a singleton Trait object etc., otherwise we have a conflict + if (!existing_signature->IsDynamicWithOwner("Trait") || + (existing_signature->value_mask_ != (kEidosValueMaskObject | kEidosValueMaskSingleton)) || + (existing_signature->value_class_ != gSLiM_Trait_Class) || (existing_signature->read_only_ == false)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Species class, but the name '" << name << "' conflicts with an existing property on Species. A different name must be used for this trait." << EidosTerminate(); + + // no conflict, so we don't need to do anything; a different species has already registered the property + } + else + { + EidosPropertySignature_CSP signature((new EidosPropertySignature(name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); + + gSLiM_Species_Class->AddSignatureForProperty(signature); + } + } + + { + // add an Individual property that returns the phenotype for the trait in an individual + const EidosPropertySignature *existing_signature = gSLiM_Individual_Class->SignatureForProperty(trait_stringID); + + if (existing_signature) + { + if (!existing_signature->IsDynamicWithOwner("Trait") || + (existing_signature->value_mask_ != (kEidosValueMaskFloat | kEidosValueMaskSingleton)) || + (existing_signature->read_only_ == true)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Individual class, but the name '" << name << "' conflicts with an existing property on Individual. A different name must be used for this trait." << EidosTerminate(); + } + else + { + EidosPropertySignature_CSP signature((new EidosPropertySignature(name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); + + gSLiM_Individual_Class->AddSignatureForProperty(signature); + } + } + + // FIXME MULTITRAIT: auto-complete on trait names off of "sim" or individuals doesn't presently work; the initializeTrait() call should add entries to the autocompletion mechanism somehow! + if (SLiM_verbosity_level >= 1) { std::ostream &output_stream = p_interpreter.ExecutionOutputStream(); @@ -2095,7 +2149,15 @@ EidosValue_SP Species::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: + { + // Here we implement a special behavior: you can do species.traitName to access a trait object directly. + Trait *trait = TraitFromStringID(p_property_id); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(trait, gSLiM_Trait_Class)); + return super::GetProperty(p_property_id); + } } } diff --git a/eidos/eidos_class_Object.cpp b/eidos/eidos_class_Object.cpp index 360fbbfd..7ce78123 100644 --- a/eidos/eidos_class_Object.cpp +++ b/eidos/eidos_class_Object.cpp @@ -527,6 +527,42 @@ void EidosClass::RaiseForDispatchUninitialized(void) const EIDOS_TERMINATION << "ERROR (EidosClass::RaiseForDispatchUninitialized): (internal error) dispatch tables not initialized for class " << ClassName() << "." << EidosTerminate(nullptr); } +void EidosClass::AddSignatureForProperty(EidosPropertySignature_CSP p_property_signature) +{ +#if DEBUG + if (!dispatches_cached_) + RaiseForDispatchUninitialized(); +#endif + + EidosGlobalStringID property_id = p_property_signature->property_id_; + + if (property_id < (EidosGlobalStringID)property_signatures_dispatch_capacity_) + { + // The property id fits into our existing dispatch table, so we can just fill it in. + // However, it is an error if this slot in the dispatch table is already in use. + if (property_signatures_dispatch_[property_id]) + EIDOS_TERMINATION << "ERROR (EidosClass::AddSignatureForProperty): (internal error) dispatch table slot is already in use for property name '" << p_property_signature->property_name_ << "'." << EidosTerminate(nullptr); + + property_signatures_dispatch_[property_id] = p_property_signature; + } + else + { + // The property id does not fit into the existing dispatch table, so we need to realloc, zero + // out all the new entries in the expanded dispatch table, and set the requested entry. Note + // that for dynamically generated property ids, the dispatch table might get a lot bigger! + int32_t new_capacity = std::max(property_signatures_dispatch_capacity_, (int32_t)property_id) + 1; + + property_signatures_dispatch_ = (EidosPropertySignature_CSP *)realloc(property_signatures_dispatch_, new_capacity * sizeof(EidosPropertySignature_CSP)); + if (!property_signatures_dispatch_) + EIDOS_TERMINATION << "ERROR (EidosClass::AddSignatureForProperty): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + + EIDOS_BZERO(property_signatures_dispatch_ + property_signatures_dispatch_capacity_, (new_capacity - property_signatures_dispatch_capacity_) * sizeof(EidosPropertySignature_CSP)); + property_signatures_dispatch_capacity_ = new_capacity; + + property_signatures_dispatch_[property_id] = p_property_signature; + } +} + const std::vector *EidosClass::Properties(void) const { static std::vector *properties = nullptr; @@ -709,21 +745,6 @@ EidosValue_SP EidosClass::ExecuteMethod_size_length(EidosGlobalStringID p_method return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(p_target->Count())); } -EidosValue_SP EidosClass::GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size) const -{ -#pragma unused (p_property_id, p_targets, p_targets_size) - - // This is the backstop, called by subclasses - EIDOS_TERMINATION << "ERROR (EidosObject::GetProperty_NO_SIGNATURE): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ClassNameForDisplay() << "." << EidosTerminate(nullptr); -} - -void EidosClass::SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size, const EidosValue &p_value) const -{ -#pragma unused (p_property_id, p_targets, p_targets_size, p_value) - - // This is the backstop, called by subclasses - EIDOS_TERMINATION << "ERROR (EidosObject::SetProperty_NO_SIGNATURE): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ClassNameForDisplay() << "." << EidosTerminate(nullptr); -} diff --git a/eidos/eidos_class_Object.h b/eidos/eidos_class_Object.h index 4fe87e2c..3e791652 100644 --- a/eidos/eidos_class_Object.h +++ b/eidos/eidos_class_Object.h @@ -174,6 +174,9 @@ class EidosClass void CacheDispatchTables(void); void RaiseForDispatchUninitialized(void) const __attribute__((__noreturn__)) __attribute__((analyzer_noreturn)); + void AddSignatureForProperty(EidosPropertySignature_CSP p_property_signature); + //void AddSignatureForMethod(EidosMethodSignature_CSP p_method_signature); // haven't needed this so far, but it could be done... + inline __attribute__((always_inline)) const EidosPropertySignature *SignatureForProperty(EidosGlobalStringID p_property_id) const { #if DEBUG @@ -206,9 +209,6 @@ class EidosClass EidosValue_SP ExecuteMethod_propertySignature(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_methodSignature(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_size_length(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; - - virtual EidosValue_SP GetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size) const; - virtual void SetProperty_NO_SIGNATURE(EidosGlobalStringID p_property_id, EidosObject **p_targets, size_t p_targets_size, const EidosValue &p_value) const; }; diff --git a/eidos/eidos_functions_other.cpp b/eidos/eidos_functions_other.cpp index f223689f..20d14e3a 100644 --- a/eidos/eidos_functions_other.cpp +++ b/eidos/eidos_functions_other.cpp @@ -201,74 +201,6 @@ EidosValue_SP Eidos_ExecuteFunction_debugIndent(__attribute__((unused)) const st #endif } -static bool Eidos_IsIdentifier(const std::string &symbol_name) -{ - // checks that symbol_name is a valid identifier; this is similar to the identifier parsing code in EidosScript::Tokenize(), - // but we know the length of symbol_name ahead of time, so the UTF handling is a bit different - bool first_char = true, saw_unicode = false; - size_t pos = 0, len = symbol_name.length(); - - while (pos < len) - { - int chx = (unsigned char)symbol_name[pos]; - - // 0..9 are fine as long as it's not the first position - if (!first_char) - if ((chx >= '0') && (chx <= '9')) - { - pos++; - continue; - } - - first_char = false; - - // a..z, A..Z, _ are all fine anywhere in an identifier - if (((chx >= 'a') && (chx <= 'z')) || ((chx >= 'A') && (chx <= 'Z')) || (chx == '_')) - { - pos++; - continue; - } - - // if the high bit is set, this is the start of a UTF-8 multi-byte sequence; eat the whole sequence - // the design of this code assumes that UTF-8 sequences are compliant; checking compliance is harder - if (chx & 0x0080) - { - // we accept the current character, and now advance over the characters following it - pos++; - saw_unicode = true; - - while (pos < len) - { - int chn = (unsigned char)symbol_name[pos]; - - if ((chn & 0x00C0) == 0x00C0) // start of a new Unicode multi-byte sequence; stop // NOLINTNEXTLINE(*-branch-clone) : intentional branch clones - { - break; - } - else if (chn & 0x0080) // trailing byte of the current Unicode multi-byte sequence; eat it - { - pos++; - } - else // an ordinary character following the Unicode sequence; stop - { - break; - } - } - - // at this point, we have advanced to the character after the end of the Unicode sequence; pos++ is not needed - continue; - } - - // an illegal character was encountered - return false; - } - - if (saw_unicode && Eidos_ContainsIllegalUnicode(symbol_name)) - return false; - - return true; -} - // (void)defineConstant(string$ symbol, * x) EidosValue_SP Eidos_ExecuteFunction_defineConstant(const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -277,7 +209,7 @@ EidosValue_SP Eidos_ExecuteFunction_defineConstant(const std::vectorStringRefAtIndex_NOCAST(0, nullptr); - if (!Eidos_IsIdentifier(symbol_name)) + if (!EidosScript::Eidos_IsIdentifier(symbol_name)) EIDOS_TERMINATION << "ERROR (Eidos_ExecuteFunction_defineConstant): defineConstant() requires that symbol is a valid Eidos identifier." << EidosTerminate(nullptr); const EidosValue_SP &x_value_sp = p_arguments[1]; @@ -307,7 +239,7 @@ EidosValue_SP Eidos_ExecuteFunction_defineGlobal(const std::vectorStringRefAtIndex_NOCAST(0, nullptr); - if (!Eidos_IsIdentifier(symbol_name)) + if (!EidosScript::Eidos_IsIdentifier(symbol_name)) EIDOS_TERMINATION << "ERROR (Eidos_ExecuteFunction_defineConstant): defineConstant() requires that symbol is a valid Eidos identifier." << EidosTerminate(nullptr); const EidosValue_SP &x_value_sp = p_arguments[1]; diff --git a/eidos/eidos_property_signature.h b/eidos/eidos_property_signature.h index 52d64fcc..b1b56efd 100644 --- a/eidos/eidos_property_signature.h +++ b/eidos/eidos_property_signature.h @@ -55,7 +55,7 @@ class EidosPropertySignature bool read_only_; // true if the property is read-only, false if it is read-write EidosValueMask value_mask_; // a mask for the type returned; singleton is used, optional is not - const EidosClass *value_class_; // optional type-check for object values; used only if this is not nullptr + const EidosClass *value_class_; // optional type-check for object values; used only if this is not nullptr bool accelerated_get_; // if true, can be read using a fast-access GetProperty_Accelerated_X() method Eidos_AcceleratedPropertyGetter accelerated_getter; // a pointer to a (static member) function that handles the accelerated get @@ -64,7 +64,9 @@ class EidosPropertySignature Eidos_AcceleratedPropertySetter accelerated_setter; // a pointer to a (static member) function that handles the accelerated set bool deprecated_ = false; // if true, the API represented by this signature has been deprecated - + + std::string dynamic_owner_; // if non-empty, indicates a dynamically generated property owned by a given owner + EidosPropertySignature(const EidosPropertySignature&) = delete; // no copying EidosPropertySignature& operator=(const EidosPropertySignature&) = delete; // no copying EidosPropertySignature(void) = delete; // no null construction @@ -88,6 +90,11 @@ class EidosPropertySignature // API deprecation; this prevents deprecated API from being shown in code completion, etc., even though it remains in the doc EidosPropertySignature *MarkDeprecated(void); + + // Dynamic property generation; the goal is to prevent dynamically generated properties from conflicting + // with built-in properties, or with each other, so we mark them with an "owner" string for recognition + EidosPropertySignature *MarkAsDynamicWithOwner(std::string p_owner) { dynamic_owner_ = p_owner; return this; } + bool IsDynamicWithOwner(std::string p_owner) const { return (!dynamic_owner_.empty() && (dynamic_owner_ == p_owner)); } }; // These typedefs for shared_ptrs of these classes should generally be used; all signature objects should be under shared_ptr now. diff --git a/eidos/eidos_script.cpp b/eidos/eidos_script.cpp index 4891e5f7..b160d864 100644 --- a/eidos/eidos_script.cpp +++ b/eidos/eidos_script.cpp @@ -101,6 +101,74 @@ EidosScript::~EidosScript(void) } } +bool EidosScript::Eidos_IsIdentifier(const std::string &symbol_name) +{ + // checks that symbol_name is a valid identifier; this is similar to the identifier parsing code in EidosScript::Tokenize(), + // but we know the length of symbol_name ahead of time, so the UTF handling is a bit different + bool first_char = true, saw_unicode = false; + size_t pos = 0, len = symbol_name.length(); + + while (pos < len) + { + int chx = (unsigned char)symbol_name[pos]; + + // 0..9 are fine as long as it's not the first position + if (!first_char) + if ((chx >= '0') && (chx <= '9')) + { + pos++; + continue; + } + + first_char = false; + + // a..z, A..Z, _ are all fine anywhere in an identifier + if (((chx >= 'a') && (chx <= 'z')) || ((chx >= 'A') && (chx <= 'Z')) || (chx == '_')) + { + pos++; + continue; + } + + // if the high bit is set, this is the start of a UTF-8 multi-byte sequence; eat the whole sequence + // the design of this code assumes that UTF-8 sequences are compliant; checking compliance is harder + if (chx & 0x0080) + { + // we accept the current character, and now advance over the characters following it + pos++; + saw_unicode = true; + + while (pos < len) + { + int chn = (unsigned char)symbol_name[pos]; + + if ((chn & 0x00C0) == 0x00C0) // start of a new Unicode multi-byte sequence; stop // NOLINTNEXTLINE(*-branch-clone) : intentional branch clones + { + break; + } + else if (chn & 0x0080) // trailing byte of the current Unicode multi-byte sequence; eat it + { + pos++; + } + else // an ordinary character following the Unicode sequence; stop + { + break; + } + } + + // at this point, we have advanced to the character after the end of the Unicode sequence; pos++ is not needed + continue; + } + + // an illegal character was encountered + return false; + } + + if (saw_unicode && Eidos_ContainsIllegalUnicode(symbol_name)) + return false; + + return true; +} + void EidosScript::Tokenize(bool p_make_bad_tokens, bool p_keep_nonsignificant) { THREAD_SAFETY_IN_ACTIVE_PARALLEL("EidosScript::Tokenize(): token_stream_ change"); diff --git a/eidos/eidos_script.h b/eidos/eidos_script.h index 39c4c16d..b21a0e48 100644 --- a/eidos/eidos_script.h +++ b/eidos/eidos_script.h @@ -88,6 +88,8 @@ class EidosScript virtual ~EidosScript(void); + static bool Eidos_IsIdentifier(const std::string &symbol_name); + void SetFinalSemicolonOptional(bool p_optional_semicolon) { final_semicolon_optional_ = p_optional_semicolon; } inline EidosScript *UserScript(void) const { return user_script_; } diff --git a/eidos/eidos_value.cpp b/eidos/eidos_value.cpp index 659b3fc0..77602090 100644 --- a/eidos/eidos_value.cpp +++ b/eidos/eidos_value.cpp @@ -2171,23 +2171,8 @@ EidosValue_SP EidosValue_Object::GetPropertyOfElements(EidosGlobalStringID p_pro size_t values_size = count_; const EidosPropertySignature *signature = class_->SignatureForProperty(p_property_id); - // BCH 6/29/2025: To enable the special trait properties of Individual, we now allow - // property access to occur without a signature, and thus without type checks. This - // goes through a special vectorized method, not through GetProperty()! if (!signature) - { - // Note that in this NO_SIGNATURE code path we don't check for a zero-length target and return a zero-length - // result; since we have no signature, we have no way to know what type that result should be. The class - // implementation of GetProperty_NO_SIGNATURE() will handle the zero-length case. - const EidosClass *target_class = class_; - EidosValue_SP result = target_class->GetProperty_NO_SIGNATURE(p_property_id, values_, values_size); - - // Access of singleton properties retains the matrix/array structure of the target - if (signature->value_mask_ & kEidosValueMaskSingleton) - result->CopyDimensionsFromValue(this); - - return result; - } + EIDOS_TERMINATION << "ERROR (EidosValue_Object::GetPropertyOfElements): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ElementType() << "." << EidosTerminate(nullptr); if (values_size == 0) { @@ -2317,29 +2302,12 @@ EidosValue_SP EidosValue_Object::GetPropertyOfElements(EidosGlobalStringID p_pro void EidosValue_Object::SetPropertyOfElements(EidosGlobalStringID p_property_id, const EidosValue &p_value, EidosToken *p_property_token) { -#pragma unused(p_property_token) - const EidosPropertySignature *signature = class_->SignatureForProperty(p_property_id); + const EidosPropertySignature *signature = Class()->SignatureForProperty(p_property_id); - // BCH 6/29/2025: To enable the special trait properties of Individual, we now allow - // property access to occur without a signature, and thus without type checks. This - // goes through a special vectorized method, not through SetProperty()! + // BCH 9 Sept. 2022: if the property does not exist, raise an error on the token for the property name. + // Note that other errors stemming from this call will refer to whatever the current error range is. if (!signature) - { - // We have to check the count ourselves; the signature does not do that for us - const EidosClass *target_class = class_; - size_t p_value_count = p_value.Count(); - size_t values_size = count_; - - // we have a multiplex assignment of one value to (maybe) more than one element: x.foo = 10 - // OR, we have a one-to-one assignment of values to elements: x.foo = 1:5 (where x has 5 elements) - if ((p_value_count == 1) || (p_value_count == values_size)) - { - if (p_value_count) - target_class->SetProperty_NO_SIGNATURE(p_property_id, values_, values_size, p_value); - } - else - EIDOS_TERMINATION << "ERROR (EidosValue_Object::SetPropertyOfElements): assignment to a property requires an rvalue that is a singleton (multiplex assignment) or that has a .size() matching the .size of the lvalue." << EidosTerminate(nullptr); - } + EIDOS_TERMINATION << "ERROR (EidosValue_Object::SetPropertyOfElements): property " << EidosStringRegistry::StringForGlobalStringID(p_property_id) << " is not defined for object element type " << ElementType() << "." << EidosTerminate(p_property_token); signature->CheckAssignedValue(p_value); // will raise if the type being assigned in is not an exact match From ed77f5eab430030701150c1eb8faa5c4b423134c Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 13 Oct 2025 14:15:43 -0400 Subject: [PATCH 013/107] make code completion on dynamic trait properties work --- EidosScribe/EidosTextView.mm | 7 +++-- QtSLiM/QtSLiMScriptTextEdit.cpp | 7 +++-- VERSIONS | 1 + core/slim_eidos_block.cpp | 23 ++++++++++++++ core/slim_eidos_block.h | 3 ++ core/species_eidos.cpp | 4 ++- eidos/eidos_class_Object.cpp | 51 ++++++++++++++++++++++++++++++++ eidos/eidos_class_Object.h | 19 ++++++++++++ eidos/eidos_symbol_table.cpp | 3 ++ eidos/eidos_symbol_table.h | 3 ++ eidos/eidos_type_interpreter.cpp | 6 +++- eidos/eidos_type_table.cpp | 4 +++ 12 files changed, 125 insertions(+), 6 deletions(-) diff --git a/EidosScribe/EidosTextView.mm b/EidosScribe/EidosTextView.mm index 4cc26d55..9ad8288e 100644 --- a/EidosScribe/EidosTextView.mm +++ b/EidosScribe/EidosTextView.mm @@ -2005,7 +2005,7 @@ - (NSMutableArray *)completionsForKeyPathEndingInTokenIndex:(int)lastDotTokenInd else { // We have a property; look up its signature and get the class - const EidosPropertySignature *property_signature = key_path_class->SignatureForProperty(identifier_id); + const EidosPropertySignature *property_signature = key_path_class->SignatureForProperty_TYPE_INTERPRETER(identifier_id); if (!property_signature) return nil; // no signature, so the class does not support the property given @@ -2023,7 +2023,7 @@ - (NSMutableArray *)completionsForKeyPathEndingInTokenIndex:(int)lastDotTokenInd const EidosClass *terminus = key_path_class; // First, a sorted list of globals - for (auto symbol_sig : *terminus->Properties()) + for (auto symbol_sig : terminus->Properties_TYPE_INTERPRETER()) { if (!symbol_sig->deprecated_) [candidates addObject:[NSString stringWithUTF8String:symbol_sig->property_name_.c_str()]]; @@ -2337,6 +2337,9 @@ - (void)_completionHandlerWithRangeForCompletion:(NSRange *)baseRange completion std::cout << "Eidos AST:\n" << parse_stream.str() << std::endl << std::endl; #endif + // Clear out dynamic property signatures kept by EidosClass, since we're starting a new type-interpretation pass. + EidosClass::ClearDynamicSignatures(); + EidosTypeInterpreter typeInterpreter(script, *typeTablePtr, *functionMapPtr, *callTypeTablePtr); typeInterpreter.TypeEvaluateInterpreterBlock_AddArgumentCompletions(&argumentCompletions, script_string.length()); // result not used diff --git a/QtSLiM/QtSLiMScriptTextEdit.cpp b/QtSLiM/QtSLiMScriptTextEdit.cpp index 237c4f64..e25487fc 100644 --- a/QtSLiM/QtSLiMScriptTextEdit.cpp +++ b/QtSLiM/QtSLiMScriptTextEdit.cpp @@ -1568,7 +1568,7 @@ QStringList QtSLiMTextEdit::completionsForKeyPathEndingInTokenIndexOfTokenStream else { // We have a property; look up its signature and get the class - const EidosPropertySignature *property_signature = key_path_class->SignatureForProperty(identifier_id); + const EidosPropertySignature *property_signature = key_path_class->SignatureForProperty_TYPE_INTERPRETER(identifier_id); if (!property_signature) return QStringList(); // no signature, so the class does not support the property given @@ -1586,7 +1586,7 @@ QStringList QtSLiMTextEdit::completionsForKeyPathEndingInTokenIndexOfTokenStream const EidosClass *terminus = key_path_class; // First, a sorted list of globals - for (const auto &symbol_sig : *terminus->Properties()) + for (const auto &symbol_sig : terminus->Properties_TYPE_INTERPRETER()) { if (!symbol_sig->deprecated_) candidates << QString::fromStdString(symbol_sig->property_name_); @@ -2345,6 +2345,9 @@ void QtSLiMTextEdit::_completionHandlerWithRangeForCompletion(NSRange *baseRange script.Tokenize(true, false); // make bad tokens as needed, do not keep nonsignificant tokens script.ParseInterpreterBlockToAST(true, true); // make bad nodes as needed (i.e. never raise, and produce a correct tree) + // Clear out dynamic property signatures kept by EidosClass, since we're starting a new type-interpretation pass. + EidosClass::ClearDynamicSignatures(); + EidosTypeInterpreter typeInterpreter(script, *typeTablePtr, *functionMapPtr, *callTypeTablePtr); typeInterpreter.TypeEvaluateInterpreterBlock_AddArgumentCompletions(&argumentCompletions, script_string.length()); // result not used diff --git a/VERSIONS b/VERSIONS index 6586dc4e..33842a03 100644 --- a/VERSIONS +++ b/VERSIONS @@ -56,6 +56,7 @@ multitrait branch: draw an individual's trait offsets from the trait individual-offset distributions, at the individual's moment of generation add Individual properties for each trait in the individual's species, allowing direct access to the phenotype for each trait in an individual add Species properties for each trait in the species, allowing direct access to traits in this species + make code completion work for the new dynamic properties on Species and Individual generated by initializeTrait() version 5.1 (Eidos version 4.1): diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index 4306887d..a45ca8b3 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -1807,6 +1807,9 @@ const std::vector *SLiMEidosBlock_Class::Properties( } +#ifdef EIDOS_GUI +// SLiMTypeTable and SLiMTypeInterpreter are only used in SLiMgui and QtSLiM + // // SLiMTypeTable // @@ -1996,6 +1999,25 @@ EidosTypeSpecifier SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(std: { _SetTypeForISArgumentOfClass(p_arguments[0], 'i', gSLiM_InteractionType_Class); } + else if ((p_function_name == "initializeTrait") && (argument_count >= 1)) + { + EidosASTNode *trait_name_node = p_arguments[0]; + const EidosToken *trait_name_token = trait_name_node->token_; + + if (trait_name_token->token_type_ == EidosTokenType::kTokenString) + { + // initializeTrait() has the side effect of defining dynamic properties on Species and Individual; + // we need to set up the information needed to make that work with code completion; we do that + // with AddSignatureForProperty_TYPE_INTERPRETER(), a version of AddSignatureForProperty() that + // uses scratch space belonging only to us, so we don't interfere with anything in SLiM itself. + const std::string &trait_name = trait_name_token->token_string_; + EidosPropertySignature_CSP species_signature((new EidosPropertySignature(trait_name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP individual_signature((new EidosPropertySignature(trait_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + + gSLiM_Species_Class->AddSignatureForProperty_TYPE_INTERPRETER(species_signature); + gSLiM_Individual_Class->AddSignatureForProperty_TYPE_INTERPRETER(individual_signature); + } + } return ret; } @@ -2053,6 +2075,7 @@ EidosTypeSpecifier SLiMTypeInterpreter::_TypeEvaluate_MethodCall_Internal(const return ret; } +#endif // EIDOS_GUI diff --git a/core/slim_eidos_block.h b/core/slim_eidos_block.h index 6c9ffd39..69fd2eba 100644 --- a/core/slim_eidos_block.h +++ b/core/slim_eidos_block.h @@ -255,6 +255,8 @@ class SLiMEidosBlock_Class : public EidosClass virtual const std::vector *Properties(void) const override; }; +#ifdef EIDOS_GUI +// SLiMTypeTable and SLiMTypeInterpreter are only used in SLiMgui and QtSLiM #pragma mark - #pragma mark SLiMTypeTable @@ -311,6 +313,7 @@ class SLiMTypeInterpreter : public EidosTypeInterpreter virtual EidosTypeSpecifier _TypeEvaluate_MethodCall_Internal(const EidosClass *p_target, const EidosMethodSignature *p_method_signature, const std::vector &p_arguments) override; }; +#endif // EIDOS_GUI #endif /* defined(__SLiM__slim_script_block__) */ diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index bfe90031..a8d395c6 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1712,6 +1712,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } else { + // see also SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this EidosPropertySignature_CSP signature((new EidosPropertySignature(name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); gSLiM_Species_Class->AddSignatureForProperty(signature); @@ -1731,7 +1732,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } else { - EidosPropertySignature_CSP signature((new EidosPropertySignature(name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); + // see also SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + EidosPropertySignature_CSP signature((new EidosPropertySignature(name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); gSLiM_Individual_Class->AddSignatureForProperty(signature); } diff --git a/eidos/eidos_class_Object.cpp b/eidos/eidos_class_Object.cpp index 7ce78123..86954466 100644 --- a/eidos/eidos_class_Object.cpp +++ b/eidos/eidos_class_Object.cpp @@ -746,6 +746,57 @@ EidosValue_SP EidosClass::ExecuteMethod_size_length(EidosGlobalStringID p_method } +#ifdef EIDOS_GUI +// We provide some support here for EidosTypeInterpreter to make code completion work with dynamic properties + +void EidosClass::ClearDynamicSignatures(void) +{ + std::vector classes = EidosClass::RegisteredClasses(/* p_builtin */ true, /* p_context */ true); + + for (EidosClass *one_class : classes) + one_class->dynamic_property_signatures_.clear(); +} + +void EidosClass::AddSignatureForProperty_TYPE_INTERPRETER(EidosPropertySignature_CSP p_property_signature) +{ + // if a dynamic property already exists with the given name, we assume it is the same, and just return + for (EidosPropertySignature_CSP dynamic_property : dynamic_property_signatures_) + if (dynamic_property->property_id_ == p_property_signature->property_id_) + return; + + dynamic_property_signatures_.push_back(p_property_signature); +} + +// This calls Properties() to get the built-in properties, and then adds the dynamic ones +std::vector EidosClass::Properties_TYPE_INTERPRETER(void) const +{ + std::vector properties = *Properties(); // make a local copy for ourselves to modify + + for (EidosPropertySignature_CSP dynamic_property : dynamic_property_signatures_) + properties.push_back(dynamic_property); + + std::sort(properties.begin(), properties.end(), CompareEidosPropertySignatures); + + return properties; +} + +// This calls SignatureForProperty(), and then checks the dynamic ones if that failed +const EidosPropertySignature *EidosClass::SignatureForProperty_TYPE_INTERPRETER(EidosGlobalStringID p_property_id) const +{ + const EidosPropertySignature *signature = SignatureForProperty(p_property_id); + + if (signature) + return signature; + + for (EidosPropertySignature_CSP dynamic_property : dynamic_property_signatures_) + if (dynamic_property->property_id_ == p_property_id) + return dynamic_property.get(); + + return nullptr; +} + +#endif // EIDOS_GUI + diff --git a/eidos/eidos_class_Object.h b/eidos/eidos_class_Object.h index 3e791652..0541ce58 100644 --- a/eidos/eidos_class_Object.h +++ b/eidos/eidos_class_Object.h @@ -209,6 +209,25 @@ class EidosClass EidosValue_SP ExecuteMethod_propertySignature(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_methodSignature(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_size_length(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + +#ifdef EIDOS_GUI + // We provide some support here for EidosTypeInterpreter to make code completion work with dynamic properties + + // This is scratch space for dynamic property signatures generated as a side effect of type-interpretation + std::vector dynamic_property_signatures_; + + // This clears out dynamic_property_signatures_ for all registered classes, to reset type-interpreter state. + static void ClearDynamicSignatures(void); + + // This adds a signature to the EidosTypeInterpreter scratch space above + void AddSignatureForProperty_TYPE_INTERPRETER(EidosPropertySignature_CSP p_property_signature); + + // This calls Properties() to get the built-in properties, and then adds the dynamic ones + std::vector Properties_TYPE_INTERPRETER(void) const; + + // This calls SignatureForProperty(), and then checks the dynamic ones if that failed + const EidosPropertySignature *SignatureForProperty_TYPE_INTERPRETER(EidosGlobalStringID p_property_id) const; +#endif // EIDOS_GUI }; diff --git a/eidos/eidos_symbol_table.cpp b/eidos/eidos_symbol_table.cpp index 4311f1f1..3374e0da 100644 --- a/eidos/eidos_symbol_table.cpp +++ b/eidos/eidos_symbol_table.cpp @@ -844,6 +844,8 @@ void EidosSymbolTable::PrintSymbolTableChain(std::ostream &p_outstream) p_outstream << "================================================" << std::endl; } +#ifdef EIDOS_GUI +// EidosTypeTable and EidosTypeInterpreter are only used in EidosScribe, SLiMguiLegacy, and QtSLiM void EidosSymbolTable::AddSymbolsToTypeTable(EidosTypeTable *p_type_table) const { // recurse to get the symbols from our chained symbol table @@ -863,6 +865,7 @@ void EidosSymbolTable::AddSymbolsToTypeTable(EidosTypeTable *p_type_table) const symbol = slot->next_; } } +#endif // EIDOS_GUI // This stream output method for EidosSymbolTable dumps all available symbols std::ostream &operator<<(std::ostream &p_outstream, const EidosSymbolTable &p_symbols) diff --git a/eidos/eidos_symbol_table.h b/eidos/eidos_symbol_table.h index 57f4cbdd..25f5e72c 100644 --- a/eidos/eidos_symbol_table.h +++ b/eidos/eidos_symbol_table.h @@ -217,8 +217,11 @@ class EidosSymbolTable void PrintSymbolTable(std::ostream &p_outstream); void PrintSymbolTableChain(std::ostream &p_outstream); +#ifdef EIDOS_GUI + // EidosTypeTable and EidosTypeInterpreter are only used in EidosScribe, SLiMguiLegacy, and QtSLiM // A utility method to add entries for defined symbols into an EidosTypeTable void AddSymbolsToTypeTable(EidosTypeTable *p_type_table) const; +#endif // EIDOS_GUI // Direct access to the symbol table chain. This should only be necessary for clients that are manipulating // the symbol table chain themselves in some way, since normally the chain is encapsulated by this class. diff --git a/eidos/eidos_type_interpreter.cpp b/eidos/eidos_type_interpreter.cpp index 0d81591a..48587db6 100644 --- a/eidos/eidos_type_interpreter.cpp +++ b/eidos/eidos_type_interpreter.cpp @@ -18,6 +18,9 @@ // You should have received a copy of the GNU General Public License along with Eidos. If not, see . +#ifdef EIDOS_GUI +// EidosTypeTable and EidosTypeInterpreter are only used in EidosScribe, SLiMguiLegacy, and QtSLiM + #include "eidos_type_interpreter.h" #include "eidos_functions.h" #include "eidos_ast_node.h" @@ -556,7 +559,7 @@ EidosTypeSpecifier EidosTypeInterpreter::TypeEvaluate_MemberRef(const EidosASTNo if (second_child_token->token_type_ == EidosTokenType::kTokenIdentifier) { EidosGlobalStringID property_string_ID = second_child_node->cached_stringID_; - const EidosPropertySignature *property_signature = first_child_type.object_class->SignatureForProperty(property_string_ID); + const EidosPropertySignature *property_signature = first_child_type.object_class->SignatureForProperty_TYPE_INTERPRETER(property_string_ID); if (property_signature) { @@ -1205,6 +1208,7 @@ EidosTypeSpecifier EidosTypeInterpreter::TypeEvaluate_FunctionDecl(const EidosAS return result_type; } +#endif // EIDOS_GUI diff --git a/eidos/eidos_type_table.cpp b/eidos/eidos_type_table.cpp index 420f3f75..93ed60b4 100644 --- a/eidos/eidos_type_table.cpp +++ b/eidos/eidos_type_table.cpp @@ -18,6 +18,9 @@ // You should have received a copy of the GNU General Public License along with Eidos. If not, see . +#ifdef EIDOS_GUI +// EidosTypeTable and EidosTypeInterpreter are only used in EidosScribe, SLiMguiLegacy, and QtSLiM + #include "eidos_type_table.h" #include @@ -165,6 +168,7 @@ std::ostream &operator<<(std::ostream &p_outstream, const EidosTypeTable &p_symb return p_outstream; } +#endif // EIDOS_GUI From f3e137d51bf61ee69a671c3fa0098b567afce494 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 13 Oct 2025 14:51:41 -0400 Subject: [PATCH 014/107] fix CMake EIDOS_GUI flag to fix build failure --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7a150c6..4009b07f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -417,7 +417,7 @@ if(BUILD_SLIMGUI) file(GLOB_RECURSE QTSLIM_SOURCES ${PROJECT_SOURCE_DIR}/QtSLiM/*.cpp ${PROJECT_SOURCE_DIR}/QtSLiM/*.qrc ${PROJECT_SOURCE_DIR}/eidos/*.cpp) add_executable(${TARGET_NAME_SLIMGUI} "${QTSLIM_SOURCES}" "${SLIM_SOURCES}") set_target_properties( ${TARGET_NAME_SLIMGUI} PROPERTIES LINKER_LANGUAGE CXX) - target_compile_definitions( ${TARGET_NAME_SLIMGUI} PRIVATE EIDOSGUI=1 SLIMGUI=1) + target_compile_definitions( ${TARGET_NAME_SLIMGUI} PRIVATE EIDOS_GUI=1 SLIMGUI=1) target_include_directories(${TARGET_NAME_SLIMGUI} PUBLIC ${GSL_INCLUDES} "${PROJECT_SOURCE_DIR}/QtSLiM" "${PROJECT_SOURCE_DIR}/eidos" "${PROJECT_SOURCE_DIR}/core" "${PROJECT_SOURCE_DIR}/treerec" "${PROJECT_SOURCE_DIR}/treerec/tskit/kastore") # Qt dependencies, which depend on the Qt version used. For Qt6, we also need C++17; the last -std flag supplied ought to take priority. From 4dcaf8fda5c9eed05ee4f5d27dbdd2f797c54368 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 13 Oct 2025 20:33:36 -0400 Subject: [PATCH 015/107] add trait value storage and accelerated trait property get/set --- core/genomic_element.cpp | 12 +- core/genomic_element.h | 8 +- core/genomic_element_type.cpp | 6 +- core/genomic_element_type.h | 4 +- core/haplosome.cpp | 15 +- core/haplosome.h | 10 +- core/individual.cpp | 242 +++++++++++++++++++++--------- core/individual.h | 95 ++++++------ core/interaction_type.cpp | 6 +- core/interaction_type.h | 4 +- core/mutation.cpp | 42 ++++-- core/mutation.h | 28 ++-- core/mutation_type.cpp | 12 +- core/mutation_type.h | 10 +- core/slim_test_genetics.cpp | 8 +- core/species_eidos.cpp | 2 +- core/subpopulation.cpp | 35 +++-- core/subpopulation.h | 18 +-- core/substitution.cpp | 33 ++-- core/substitution.h | 22 +-- eidos/eidos_class_TestElement.cpp | 6 +- eidos/eidos_class_TestElement.h | 4 +- eidos/eidos_property_signature.h | 4 +- eidos/eidos_value.cpp | 6 +- 24 files changed, 395 insertions(+), 237 deletions(-) diff --git a/core/genomic_element.cpp b/core/genomic_element.cpp index 7b35e110..bec96162 100644 --- a/core/genomic_element.cpp +++ b/core/genomic_element.cpp @@ -101,8 +101,9 @@ EidosValue_SP GenomicElement::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *GenomicElement::GetProperty_Accelerated_startPosition(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElement::GetProperty_Accelerated_startPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -115,8 +116,9 @@ EidosValue *GenomicElement::GetProperty_Accelerated_startPosition(EidosObject ** return int_result; } -EidosValue *GenomicElement::GetProperty_Accelerated_endPosition(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElement::GetProperty_Accelerated_endPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -129,8 +131,9 @@ EidosValue *GenomicElement::GetProperty_Accelerated_endPosition(EidosObject **p_ return int_result; } -EidosValue *GenomicElement::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElement::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -147,8 +150,9 @@ EidosValue *GenomicElement::GetProperty_Accelerated_tag(EidosObject **p_values, return int_result; } -EidosValue *GenomicElement::GetProperty_Accelerated_genomicElementType(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElement::GetProperty_Accelerated_genomicElementType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Object *object_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_GenomicElementType_Class))->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) diff --git a/core/genomic_element.h b/core/genomic_element.h index d40c5db1..8b67e05a 100644 --- a/core/genomic_element.h +++ b/core/genomic_element.h @@ -76,10 +76,10 @@ class GenomicElement : public EidosObject EidosValue_SP ExecuteMethod_setGenomicElementType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_startPosition(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_endPosition(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_genomicElementType(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_startPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_endPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_genomicElementType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); }; // support stream output of GenomicElement, for debugging diff --git a/core/genomic_element_type.cpp b/core/genomic_element_type.cpp index c86d016f..23733d8b 100644 --- a/core/genomic_element_type.cpp +++ b/core/genomic_element_type.cpp @@ -301,8 +301,9 @@ EidosValue_SP GenomicElementType::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *GenomicElementType::GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElementType::GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -315,8 +316,9 @@ EidosValue *GenomicElementType::GetProperty_Accelerated_id(EidosObject **p_value return int_result; } -EidosValue *GenomicElementType::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *GenomicElementType::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) diff --git a/core/genomic_element_type.h b/core/genomic_element_type.h index fca94114..4c2300a1 100644 --- a/core/genomic_element_type.h +++ b/core/genomic_element_type.h @@ -102,8 +102,8 @@ class GenomicElementType : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_setMutationMatrix(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); }; // support stream output of GenomicElementType, for debugging diff --git a/core/haplosome.cpp b/core/haplosome.cpp index e50e13bf..f667169a 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -481,8 +481,9 @@ EidosValue_SP Haplosome::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *Haplosome::GetProperty_Accelerated_haplosomePedigreeID(EidosObject **p_values, size_t p_values_size) +EidosValue *Haplosome::GetProperty_Accelerated_haplosomePedigreeID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); size_t value_index = 0; @@ -508,8 +509,9 @@ EidosValue *Haplosome::GetProperty_Accelerated_haplosomePedigreeID(EidosObject * return int_result; } -EidosValue *Haplosome::GetProperty_Accelerated_chromosomeSubposition(EidosObject **p_values, size_t p_values_size) +EidosValue *Haplosome::GetProperty_Accelerated_chromosomeSubposition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -523,8 +525,9 @@ EidosValue *Haplosome::GetProperty_Accelerated_chromosomeSubposition(EidosObject return int_result; } -EidosValue *Haplosome::GetProperty_Accelerated_isNullHaplosome(EidosObject **p_values, size_t p_values_size) +EidosValue *Haplosome::GetProperty_Accelerated_isNullHaplosome(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -537,8 +540,9 @@ EidosValue *Haplosome::GetProperty_Accelerated_isNullHaplosome(EidosObject **p_v return logical_result; } -EidosValue *Haplosome::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *Haplosome::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -575,8 +579,9 @@ void Haplosome::SetProperty(EidosGlobalStringID p_property_id, const EidosValue } } -void Haplosome::SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Haplosome::SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) Individual::s_any_haplosome_tag_set_ = true; // SLiMCastToUsertagTypeOrRaise() is a no-op at present diff --git a/core/haplosome.h b/core/haplosome.h index 48e574bc..30b95464 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -437,13 +437,13 @@ class Haplosome : public EidosObject EidosValue_SP ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_haplosomePedigreeID(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_chromosomeSubposition(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_isNullHaplosome(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_haplosomePedigreeID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_chromosomeSubposition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_isNullHaplosome(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); // Accelerated property writing; see class EidosObject for comments on this mechanism - static void SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); friend class Haplosome_Class; diff --git a/core/individual.cpp b/core/individual.cpp index 616d6b6e..69317e4c 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -84,7 +84,7 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu } // Set up per-trait information such as phenotype caches and individual offsets - _DrawTraitOffsets(); + _InitializePerTraitInformation(); // Initialize tag values to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; @@ -103,7 +103,7 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu #endif } -void Individual::_DrawTraitOffsets(void) +void Individual::_InitializePerTraitInformation(void) { // Set up per-trait individual-level information such as individual offsets. This is called by // Individual::Individual(), but also in various other places where individuals are re-used. @@ -133,19 +133,23 @@ void Individual::_DrawTraitOffsets(void) if (trait_count == 1) { - offsets_for_traits_ = &offset_for_trait_0_; - offset_for_trait_0_ = traits[0]->DrawIndividualOffset(); + trait_info_ = &trait_info_0_; + trait_info_0_.value_ = 0.0; + trait_info_0_.offset_ = traits[0]->DrawIndividualOffset(); } else if (trait_count == 0) { - offsets_for_traits_ = nullptr; + trait_info_ = nullptr; } else { - offsets_for_traits_ = static_cast(malloc(trait_count * sizeof(slim_effect_t))); + trait_info_ = static_cast(malloc(trait_count * sizeof(SLiM_PerTraitInfo))); for (int trait_index = 0; trait_index < trait_count; ++trait_index) - offsets_for_traits_[trait_index] = traits[trait_index]->DrawIndividualOffset(); + { + trait_info_[trait_index].value_ = 0.0; + trait_info_[trait_index].offset_ = traits[trait_index]->DrawIndividualOffset(); + } } } @@ -181,12 +185,12 @@ Individual::~Individual(void) if (haplosomes_ != hapbuffer_) free(haplosomes_); - if (offsets_for_traits_ != &offset_for_trait_0_) - free(offsets_for_traits_); + if (trait_info_ != &trait_info_0_) + free(trait_info_); #if DEBUG haplosomes_ = nullptr; - offsets_for_traits_ = nullptr; + trait_info_ = nullptr; #endif } @@ -1726,17 +1730,7 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) if (trait) { - // We got a hit, but don't know what to do with it for now. When this is hooked up to the trait value, - // I should add an accelerated getter since vectorized access for these trait properties will be very common. - // That will require modifying the accelerated getter mechanism to pass the property id in to the method, though. - // It would do: - //Species *species = Community::SpeciesForIndividualsVector(individuals, (int)p_targets_size); - //Trait *trait = species->TraitFromStringID(p_property_id); - // If the individuals don't belong to a single species, it would look up the species for each individual to get the right trait, I guess, - // since two species could have traits with the same name but at different trait indices, so you have to look up the right index. - EIDOS_TERMINATION << "ERROR (Individual::GetProperty): trait " << trait->Name() << " cannot be accessed (FIXME MULTITRAIT)." << EidosTerminate(); - // we want something like this - //return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_value)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].value_)); } return super::GetProperty(p_property_id); @@ -1744,8 +1738,9 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *Individual::GetProperty_Accelerated_index(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_index(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1758,8 +1753,9 @@ EidosValue *Individual::GetProperty_Accelerated_index(EidosObject **p_values, si return int_result; } -EidosValue *Individual::GetProperty_Accelerated_pedigreeID(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_pedigreeID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); size_t value_index = 0; @@ -1785,8 +1781,9 @@ EidosValue *Individual::GetProperty_Accelerated_pedigreeID(EidosObject **p_value return int_result; } -EidosValue *Individual::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1803,8 +1800,9 @@ EidosValue *Individual::GetProperty_Accelerated_tag(EidosObject **p_values, size return int_result; } -EidosValue *Individual::GetProperty_Accelerated_age(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_age(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) if ((p_values_size > 0) && (((Individual *)(p_values[0]))->subpopulation_->community_.ModelType() == SLiMModelType::kModelTypeWF)) EIDOS_TERMINATION << "ERROR (Individual::GetProperty): property age is not available in WF models." << EidosTerminate(); @@ -1820,8 +1818,9 @@ EidosValue *Individual::GetProperty_Accelerated_age(EidosObject **p_values, size return int_result; } -EidosValue *Individual::GetProperty_Accelerated_reproductiveOutput(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_reproductiveOutput(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) if ((p_values_size > 0) && !((Individual *)(p_values[0]))->subpopulation_->species_.PedigreesEnabledByUser()) EIDOS_TERMINATION << "ERROR (Individual::GetProperty): property reproductiveOutput is not available because pedigree recording has not been enabled." << EidosTerminate(); @@ -1837,8 +1836,9 @@ EidosValue *Individual::GetProperty_Accelerated_reproductiveOutput(EidosObject * return int_result; } -EidosValue *Individual::GetProperty_Accelerated_tagF(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_tagF(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1855,8 +1855,9 @@ EidosValue *Individual::GetProperty_Accelerated_tagF(EidosObject **p_values, siz return float_result; } -EidosValue *Individual::GetProperty_Accelerated_tagL0(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_tagL0(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1872,8 +1873,9 @@ EidosValue *Individual::GetProperty_Accelerated_tagL0(EidosObject **p_values, si return logical_result; } -EidosValue *Individual::GetProperty_Accelerated_tagL1(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_tagL1(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1889,8 +1891,9 @@ EidosValue *Individual::GetProperty_Accelerated_tagL1(EidosObject **p_values, si return logical_result; } -EidosValue *Individual::GetProperty_Accelerated_tagL2(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_tagL2(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1906,8 +1909,9 @@ EidosValue *Individual::GetProperty_Accelerated_tagL2(EidosObject **p_values, si return logical_result; } -EidosValue *Individual::GetProperty_Accelerated_tagL3(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_tagL3(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1923,8 +1927,9 @@ EidosValue *Individual::GetProperty_Accelerated_tagL3(EidosObject **p_values, si return logical_result; } -EidosValue *Individual::GetProperty_Accelerated_tagL4(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_tagL4(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1940,8 +1945,9 @@ EidosValue *Individual::GetProperty_Accelerated_tagL4(EidosObject **p_values, si return logical_result; } -EidosValue *Individual::GetProperty_Accelerated_migrant(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_migrant(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1954,8 +1960,9 @@ EidosValue *Individual::GetProperty_Accelerated_migrant(EidosObject **p_values, return logical_result; } -EidosValue *Individual::GetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1968,8 +1975,9 @@ EidosValue *Individual::GetProperty_Accelerated_fitnessScaling(EidosObject **p_v return float_result; } -EidosValue *Individual::GetProperty_Accelerated_x(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_x(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1982,8 +1990,9 @@ EidosValue *Individual::GetProperty_Accelerated_x(EidosObject **p_values, size_t return float_result; } -EidosValue *Individual::GetProperty_Accelerated_y(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_y(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -1996,8 +2005,9 @@ EidosValue *Individual::GetProperty_Accelerated_y(EidosObject **p_values, size_t return float_result; } -EidosValue *Individual::GetProperty_Accelerated_z(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_z(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -2010,8 +2020,9 @@ EidosValue *Individual::GetProperty_Accelerated_z(EidosObject **p_values, size_t return float_result; } -EidosValue *Individual::GetProperty_Accelerated_spatialPosition(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_spatialPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) Species *consensus_species = Community::SpeciesForIndividualsVector((Individual **)p_values, (int)p_values_size); EidosValue_Float *float_result; @@ -2093,8 +2104,9 @@ EidosValue *Individual::GetProperty_Accelerated_spatialPosition(EidosObject **p_ return float_result; } -EidosValue *Individual::GetProperty_Accelerated_subpopulation(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_subpopulation(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Object *object_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Subpopulation_Class))->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -2110,8 +2122,9 @@ EidosValue *Individual::GetProperty_Accelerated_subpopulation(EidosObject **p_va return object_result; } -EidosValue *Individual::GetProperty_Accelerated_haploidGenome1(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_haploidGenome1(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) const Individual **individuals_buffer = (const Individual **)p_values; // SPECIES CONSISTENCY CHECK @@ -2178,8 +2191,9 @@ EidosValue *Individual::GetProperty_Accelerated_haploidGenome1(EidosObject **p_v EIDOS_TERMINATION << "ERROR (Individual::GetProperty_Accelerated_haploidGenome1): (internal error) chromosome type not handled." << EidosTerminate(); } -EidosValue *Individual::GetProperty_Accelerated_haploidGenome1NonNull(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_haploidGenome1NonNull(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) const Individual **individuals_buffer = (const Individual **)p_values; // SPECIES CONSISTENCY CHECK @@ -2250,8 +2264,9 @@ EidosValue *Individual::GetProperty_Accelerated_haploidGenome1NonNull(EidosObjec EIDOS_TERMINATION << "ERROR (Individual::GetProperty_Accelerated_haploidGenome1NonNull): (internal error) chromosome type not handled." << EidosTerminate(); } -EidosValue *Individual::GetProperty_Accelerated_haploidGenome2(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_haploidGenome2(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) const Individual **individuals_buffer = (const Individual **)p_values; // SPECIES CONSISTENCY CHECK @@ -2318,8 +2333,9 @@ EidosValue *Individual::GetProperty_Accelerated_haploidGenome2(EidosObject **p_v EIDOS_TERMINATION << "ERROR (Individual::GetProperty_Accelerated_haploidGenome2): (internal error) chromosome type not handled." << EidosTerminate(); } -EidosValue *Individual::GetProperty_Accelerated_haploidGenome2NonNull(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_haploidGenome2NonNull(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) const Individual **individuals_buffer = (const Individual **)p_values; // SPECIES CONSISTENCY CHECK @@ -2390,8 +2406,9 @@ EidosValue *Individual::GetProperty_Accelerated_haploidGenome2NonNull(EidosObjec EIDOS_TERMINATION << "ERROR (Individual::GetProperty_Accelerated_haploidGenome2NonNull): (internal error) chromosome type not handled." << EidosTerminate(); } -EidosValue *Individual::GetProperty_Accelerated_haplosomes(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_haplosomes(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) const Individual **individuals_buffer = (const Individual **)p_values; // SPECIES CONSISTENCY CHECK @@ -2420,8 +2437,9 @@ EidosValue *Individual::GetProperty_Accelerated_haplosomes(EidosObject **p_value return object_result; } -EidosValue *Individual::GetProperty_Accelerated_haplosomesNonNull(EidosObject **p_values, size_t p_values_size) +EidosValue *Individual::GetProperty_Accelerated_haplosomesNonNull(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) const Individual **individuals_buffer = (const Individual **)p_values; // SPECIES CONSISTENCY CHECK @@ -2450,6 +2468,41 @@ EidosValue *Individual::GetProperty_Accelerated_haplosomesNonNull(EidosObject ** return object_result; } +EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) +{ +#pragma unused (p_property_id) + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); + const Individual **individuals_buffer = (const Individual **)p_values; + Species *species = Community::SpeciesForIndividualsVector(individuals_buffer, (int)p_values_size); + + if (species) + { + Trait *trait = species->TraitFromStringID(p_property_id); + int64_t trait_index = trait->Index(); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + + float_result->set_float_no_check(value->trait_info_[trait_index].value_, value_index); + } + } + else + { + // with a mixed-species target, the species and trait have to be looked up for each individual + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); + int64_t trait_index = trait->Index(); + + float_result->set_float_no_check(value->trait_info_[trait_index].value_, value_index); + } + } + + return float_result; +} + void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) { // All of our strings are in the global registry, so we can require a successful lookup @@ -2575,10 +2628,8 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue if (trait) { - // We got a hit, but don't know what to do with it for now. - EIDOS_TERMINATION << "ERROR (Individual::SetProperty): trait " << trait->Name() << " cannot be accessed (FIXME MULTITRAIT)." << EidosTerminate(); - // we want something like this - //trait_value = p_value.FloatAtIndex_NOCAST(0, nullptr); + trait_info_[trait->Index()].value_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + return; } return super::SetProperty(p_property_id, p_value); @@ -2586,8 +2637,9 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue } } -void Individual::SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) s_any_individual_tag_set_ = true; // SLiMCastToUsertagTypeOrRaise() is a no-op at present @@ -2607,8 +2659,9 @@ void Individual::SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_va } } -void Individual::SetProperty_Accelerated_tagF(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_tagF(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) s_any_individual_tagF_set_ = true; // SLiMCastToUsertagTypeOrRaise() is a no-op at present @@ -2628,8 +2681,9 @@ void Individual::SetProperty_Accelerated_tagF(EidosObject **p_values, size_t p_v } } -void Individual::SetProperty_Accelerated_tagL0(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_tagL0(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) s_any_individual_tagL_set_ = true; const eidos_logical_t *source_data = p_source.LogicalData(); @@ -2659,8 +2713,9 @@ void Individual::SetProperty_Accelerated_tagL0(EidosObject **p_values, size_t p_ } } -void Individual::SetProperty_Accelerated_tagL1(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_tagL1(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) s_any_individual_tagL_set_ = true; const eidos_logical_t *source_data = p_source.LogicalData(); @@ -2689,8 +2744,9 @@ void Individual::SetProperty_Accelerated_tagL1(EidosObject **p_values, size_t p_ } } -void Individual::SetProperty_Accelerated_tagL2(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_tagL2(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) s_any_individual_tagL_set_ = true; const eidos_logical_t *source_data = p_source.LogicalData(); @@ -2719,8 +2775,9 @@ void Individual::SetProperty_Accelerated_tagL2(EidosObject **p_values, size_t p_ } } -void Individual::SetProperty_Accelerated_tagL3(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_tagL3(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) s_any_individual_tagL_set_ = true; const eidos_logical_t *source_data = p_source.LogicalData(); @@ -2749,8 +2806,9 @@ void Individual::SetProperty_Accelerated_tagL3(EidosObject **p_values, size_t p_ } } -void Individual::SetProperty_Accelerated_tagL4(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_tagL4(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) s_any_individual_tagL_set_ = true; const eidos_logical_t *source_data = p_source.LogicalData(); @@ -2821,8 +2879,9 @@ bool Individual::_SetFitnessScaling_N(const double *source_data, EidosObject **p return saw_error; } -void Individual::SetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) Individual::s_any_individual_fitness_scaling_set_ = true; bool needs_raise = false; @@ -2843,8 +2902,9 @@ void Individual::SetProperty_Accelerated_fitnessScaling(EidosObject **p_values, EIDOS_TERMINATION << "ERROR (Individual::SetProperty_Accelerated_fitnessScaling): property fitnessScaling must be >= 0.0." << EidosTerminate(); } -void Individual::SetProperty_Accelerated_x(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_x(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { double source_value = p_source.FloatAtIndex_NOCAST(0, nullptr); @@ -2861,8 +2921,9 @@ void Individual::SetProperty_Accelerated_x(EidosObject **p_values, size_t p_valu } } -void Individual::SetProperty_Accelerated_y(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_y(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { double source_value = p_source.FloatAtIndex_NOCAST(0, nullptr); @@ -2879,8 +2940,9 @@ void Individual::SetProperty_Accelerated_y(EidosObject **p_values, size_t p_valu } } -void Individual::SetProperty_Accelerated_z(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_z(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { double source_value = p_source.FloatAtIndex_NOCAST(0, nullptr); @@ -2897,9 +2959,9 @@ void Individual::SetProperty_Accelerated_z(EidosObject **p_values, size_t p_valu } } -void Individual::SetProperty_Accelerated_color(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_color(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { -#pragma unused (p_values, p_values_size, p_source, p_source_size) +#pragma unused (p_property_id, p_values, p_values_size, p_source, p_source_size) #ifdef SLIMGUI // BCH 3/23/2025: color variables now only exist in SLiMgui, to save on memory footprint if (p_source_size == 1) @@ -2954,8 +3016,9 @@ void Individual::SetProperty_Accelerated_color(EidosObject **p_values, size_t p_ #endif } -void Individual::SetProperty_Accelerated_age(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Individual::SetProperty_Accelerated_age(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { int64_t source_value = p_source.IntAtIndex_NOCAST(0, nullptr); @@ -2973,6 +3036,39 @@ void Individual::SetProperty_Accelerated_age(EidosObject **p_values, size_t p_va } } +void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +{ +#pragma unused (p_property_id) + const Individual **individuals_buffer = (const Individual **)p_values; + Species *species = Community::SpeciesForIndividualsVector(individuals_buffer, (int)p_values_size); + const double *source_data = p_source.FloatData(); + + if (species) + { + Trait *trait = species->TraitFromStringID(p_property_id); + int64_t trait_index = trait->Index(); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + + value->trait_info_[trait_index].value_ = (slim_effect_t)source_data[value_index]; + } + } + else + { + // with a mixed-species target, the species and trait have to be looked up for each individual + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); + int64_t trait_index = trait->Index(); + + value->trait_info_[trait_index].value_ = (slim_effect_t)source_data[value_index]; + } + } +} + EidosValue_SP Individual::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { switch (p_method_id) @@ -3177,7 +3273,7 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met if (trait_indices.size() == 1) { int64_t trait_index = trait_indices[0]; - slim_effect_t offset = offsets_for_traits_[trait_index]; + slim_effect_t offset = trait_info_[trait_index].offset_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(offset)); } @@ -3187,7 +3283,7 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met for (int64_t trait_index : trait_indices) { - slim_effect_t offset = offsets_for_traits_[trait_index]; + slim_effect_t offset = trait_info_[trait_index].offset_; float_result->push_float_no_check(offset); } @@ -4122,7 +4218,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { Individual *ind = individuals_buffer[individual_index]; - ind->offsets_for_traits_[trait_index] = offset; + ind->trait_info_[trait_index].offset_ = offset; } } } @@ -4137,7 +4233,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin int64_t trait_index = trait_indices[0]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - individuals_buffer[individual_index]->offsets_for_traits_[trait_index] = offset; + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; } else { @@ -4146,7 +4242,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin Individual *ind = individuals_buffer[individual_index]; for (int64_t trait_index : trait_indices) - ind->offsets_for_traits_[trait_index] = offset; + ind->trait_info_[trait_index].offset_ = offset; } } } @@ -4163,7 +4259,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { Individual *ind = individuals_buffer[individual_index]; - ind->offsets_for_traits_[trait_index] = offset; + ind->trait_info_[trait_index].offset_ = offset; } } } @@ -4182,7 +4278,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin int64_t trait_index = trait_indices[0]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - individuals_buffer[individual_index]->offsets_for_traits_[trait_index] = static_cast(*(offsets_int++)); + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = static_cast(*(offsets_int++)); } else { @@ -4191,7 +4287,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin Individual *ind = individuals_buffer[individual_index]; for (int64_t trait_index : trait_indices) - ind->offsets_for_traits_[trait_index] = static_cast(*(offsets_int++)); + ind->trait_info_[trait_index].offset_ = static_cast(*(offsets_int++)); } } } @@ -4206,7 +4302,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin int64_t trait_index = trait_indices[0]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - individuals_buffer[individual_index]->offsets_for_traits_[trait_index] = static_cast(*(offsets_float++)); + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = static_cast(*(offsets_float++)); } else { @@ -4215,7 +4311,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin Individual *ind = individuals_buffer[individual_index]; for (int64_t trait_index : trait_indices) - ind->offsets_for_traits_[trait_index] = static_cast(*(offsets_float++)); + ind->trait_info_[trait_index].offset_ = static_cast(*(offsets_float++)); } } } diff --git a/core/individual.h b/core/individual.h index 9c078626..ec3d0b47 100644 --- a/core/individual.h +++ b/core/individual.h @@ -63,6 +63,14 @@ inline slim_pedigreeid_t SLiM_GetNextPedigreeID_Block(int p_block_size) return block_base; } +// This struct contains all information for a single trait in a single individual. In a multitrait +// model, each individual has a pointer to a buffer of these records, providing per-trait information. +typedef struct _SLiM_PerTraitInfo +{ + slim_effect_t value_; // the phenotypic value for a trait + slim_effect_t offset_; // the individual offset combined in to produce a trait value +} SLiM_PerTraitInfo; + class Individual : public EidosDictionaryUnretained { // This class has its copy constructor and assignment operator disabled, to prevent accidental copying. @@ -150,10 +158,11 @@ class Individual : public EidosDictionaryUnretained slim_popsize_t index_; // the individual index in that subpop (0-based, and not multiplied by 2) Subpopulation *subpopulation_; // the subpop to which we belong; cannot be a reference because it changes on migration! - // Trait offsets. If the species has 0 traits offsets_for_traits_ is nullptr; if 1 trait it points to - // _offset_for_trait_0_ for memory locality; if 2+ traits it points to an OWNED malloced buffer. - slim_effect_t offset_for_trait_0_; - slim_effect_t *offsets_for_traits_; + // Per-trait information: trait offsets, trait values. If the species has 0 traits, the pointer is + // nullptr; if 1 trait, it points to trait_info_0_ for memory locality and to avoid mallocs; if 2+ + // trait, it points to an OWNED malloced buffer. + SLiM_PerTraitInfo trait_info_0_; + SLiM_PerTraitInfo *trait_info_; // Continuous space ivars. These are effectively free tag values of type float, unless they are used by interactions. double spatial_x_, spatial_y_, spatial_z_; @@ -169,7 +178,7 @@ class Individual : public EidosDictionaryUnretained Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age); virtual ~Individual(void) override; - void _DrawTraitOffsets(void); + void _InitializePerTraitInformation(void); inline __attribute__((always_inline)) void ClearColor(void) { #ifdef SLIMGUI @@ -338,47 +347,49 @@ class Individual : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_mutationsFromHaplosomes(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_index(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_pedigreeID(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tagL0(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tagL1(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tagL2(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tagL3(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tagL4(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_age(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_reproductiveOutput(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tagF(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_migrant(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_x(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_y(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_z(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_spatialPosition(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_subpopulation(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_haploidGenome1(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_haploidGenome1NonNull(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_haploidGenome2(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_haploidGenome2NonNull(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_haplosomes(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_haplosomesNonNull(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_index(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_pedigreeID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tagL0(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tagL1(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tagL2(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tagL3(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tagL4(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_age(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_reproductiveOutput(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tagF(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_migrant(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_x(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_y(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_z(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_spatialPosition(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_subpopulation(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_haploidGenome1(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_haploidGenome1NonNull(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_haploidGenome2(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_haploidGenome2NonNull(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_haplosomes(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_haplosomesNonNull(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); // Accelerated property writing; see class EidosObject for comments on this mechanism - static void SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_tagF(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_tagL0(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_tagL1(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_tagL2(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_tagL3(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_tagL4(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tagF(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tagL0(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tagL1(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tagL2(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tagL3(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tagL4(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); static bool _SetFitnessScaling_1(double source_value, EidosObject **p_values, size_t p_values_size); static bool _SetFitnessScaling_N(const double *source_data, EidosObject **p_values, size_t p_values_size); - static void SetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_x(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_y(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_z(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_age(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_color(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_x(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_y(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_z(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_color(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_age(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); // These flags are used to minimize the work done by Subpopulation::SwapChildAndParentHaplosomes(); it only needs to // reset colors or dictionaries if they have ever been touched by the model. These flags are set and never cleared. diff --git a/core/interaction_type.cpp b/core/interaction_type.cpp index c986e34c..a08ba34e 100755 --- a/core/interaction_type.cpp +++ b/core/interaction_type.cpp @@ -3527,8 +3527,9 @@ EidosValue_SP InteractionType::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *InteractionType::GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size) +EidosValue *InteractionType::GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -3541,8 +3542,9 @@ EidosValue *InteractionType::GetProperty_Accelerated_id(EidosObject **p_values, return int_result; } -EidosValue *InteractionType::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *InteractionType::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) diff --git a/core/interaction_type.h b/core/interaction_type.h index aa7cf2fe..5d6de121 100644 --- a/core/interaction_type.h +++ b/core/interaction_type.h @@ -469,8 +469,8 @@ class InteractionType : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_unevaluate(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); }; class InteractionType_Class : public EidosDictionaryUnretained_Class diff --git a/core/mutation.cpp b/core/mutation.cpp index c10a0442..4a6e9edf 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -443,8 +443,9 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *Mutation::GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -457,8 +458,9 @@ EidosValue *Mutation::GetProperty_Accelerated_id(EidosObject **p_values, size_t return int_result; } -EidosValue *Mutation::GetProperty_Accelerated_isFixed(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_isFixed(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -471,8 +473,9 @@ EidosValue *Mutation::GetProperty_Accelerated_isFixed(EidosObject **p_values, si return logical_result; } -EidosValue *Mutation::GetProperty_Accelerated_isSegregating(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_isSegregating(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -485,8 +488,9 @@ EidosValue *Mutation::GetProperty_Accelerated_isSegregating(EidosObject **p_valu return logical_result; } -EidosValue *Mutation::GetProperty_Accelerated_nucleotide(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_String *string_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_String())->Reserve((int)p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -510,8 +514,9 @@ EidosValue *Mutation::GetProperty_Accelerated_nucleotide(EidosObject **p_values, return string_result; } -EidosValue *Mutation::GetProperty_Accelerated_nucleotideValue(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_nucleotideValue(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -528,8 +533,9 @@ EidosValue *Mutation::GetProperty_Accelerated_nucleotideValue(EidosObject **p_va return int_result; } -EidosValue *Mutation::GetProperty_Accelerated_originTick(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_originTick(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -542,8 +548,9 @@ EidosValue *Mutation::GetProperty_Accelerated_originTick(EidosObject **p_values, return int_result; } -EidosValue *Mutation::GetProperty_Accelerated_position(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_position(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -556,8 +563,9 @@ EidosValue *Mutation::GetProperty_Accelerated_position(EidosObject **p_values, s return int_result; } -EidosValue *Mutation::GetProperty_Accelerated_subpopID(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_subpopID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -570,8 +578,9 @@ EidosValue *Mutation::GetProperty_Accelerated_subpopID(EidosObject **p_values, s return int_result; } -EidosValue *Mutation::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -588,8 +597,9 @@ EidosValue *Mutation::GetProperty_Accelerated_tag(EidosObject **p_values, size_t return int_result; } -EidosValue *Mutation::GetProperty_Accelerated_selectionCoeff(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_selectionCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -602,8 +612,9 @@ EidosValue *Mutation::GetProperty_Accelerated_selectionCoeff(EidosObject **p_val return float_result; } -EidosValue *Mutation::GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -616,8 +627,9 @@ EidosValue *Mutation::GetProperty_Accelerated_dominanceCoeff(EidosObject **p_val return float_result; } -EidosValue *Mutation::GetProperty_Accelerated_mutationType(EidosObject **p_values, size_t p_values_size) +EidosValue *Mutation::GetProperty_Accelerated_mutationType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Object *object_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_MutationType_Class))->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -683,8 +695,9 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & } } -void Mutation::SetProperty_Accelerated_subpopID(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Mutation::SetProperty_Accelerated_subpopID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { int64_t source_value = p_source.IntAtIndex_NOCAST(0, nullptr); @@ -701,8 +714,9 @@ void Mutation::SetProperty_Accelerated_subpopID(EidosObject **p_values, size_t p } } -void Mutation::SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Mutation::SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) // SLiMCastToUsertagTypeOrRaise() is a no-op at present if (p_source_size == 1) { diff --git a/core/mutation.h b/core/mutation.h index 66157362..b8ecfeb7 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -138,22 +138,22 @@ class Mutation : public EidosDictionaryRetained EidosValue_SP ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_isFixed(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_isSegregating(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_nucleotide(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_nucleotideValue(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_originTick(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_position(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_subpopID(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_selectionCoeff(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_mutationType(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_isFixed(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_isSegregating(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_nucleotideValue(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_originTick(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_position(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_subpopID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_selectionCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_mutationType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); // Accelerated property writing; see class EidosObject for comments on this mechanism - static void SetProperty_Accelerated_subpopID(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_subpopID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); }; // true if M1 has an earlier (smaller) position than M2 diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 17bbdd69..41b1154d 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -528,8 +528,9 @@ EidosValue_SP MutationType::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *MutationType::GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size) +EidosValue *MutationType::GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -542,8 +543,9 @@ EidosValue *MutationType::GetProperty_Accelerated_id(EidosObject **p_values, siz return int_result; } -EidosValue *MutationType::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *MutationType::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -651,8 +653,9 @@ void MutationType::SetProperty(EidosGlobalStringID p_property_id, const EidosVal } } -void MutationType::SetProperty_Accelerated_convertToSubstitution(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void MutationType::SetProperty_Accelerated_convertToSubstitution(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { eidos_logical_t source_value = p_source.LogicalAtIndex_NOCAST(0, nullptr); @@ -669,8 +672,9 @@ void MutationType::SetProperty_Accelerated_convertToSubstitution(EidosObject **p } } -void MutationType::SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void MutationType::SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) // SLiMCastToUsertagTypeOrRaise() is a no-op at present if (p_source_size == 1) { diff --git a/core/mutation_type.h b/core/mutation_type.h index 9436849b..0676cdea 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -204,12 +204,12 @@ class MutationType : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); - static void SetProperty_Accelerated_convertToSubstitution(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_convertToSubstitution(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); }; // support stream output of MutationType, for debugging diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 9697b81d..8aace2da 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1081,10 +1081,10 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.weight = sim.traitsWithNames('weight'); }", "new value for read-only property", __LINE__); // individual trait property access (not yet fully implemented) - SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.height; }", "trait height cannot be accessed (FIXME MULTITRAIT)", __LINE__); - SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.weight; }", "trait weight cannot be accessed (FIXME MULTITRAIT)", __LINE__); - SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.height = 10.0; }", "trait height cannot be accessed (FIXME MULTITRAIT)", __LINE__); - SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.weight = 10.0; }", "trait weight cannot be accessed (FIXME MULTITRAIT)", __LINE__); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.height, rep(0.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.weight, rep(0.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0; if (!identical(p1.individuals.height, rep(10.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 10.0; if (!identical(p1.individuals.weight, rep(10.0, 5))) stop(); }"); } std::cout << "_RunMultitraitTests() done" << std::endl; diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index a8d395c6..b43defd5 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1733,7 +1733,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string else { // see also SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this - EidosPropertySignature_CSP signature((new EidosPropertySignature(name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP signature((new EidosPropertySignature(name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")->DeclareAcceleratedGet(Individual::GetProperty_Accelerated_TRAIT_VALUE)); gSLiM_Individual_Class->AddSignatureForProperty(signature); } diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 30fcab15..8158324d 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -4501,7 +4501,7 @@ bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigree individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent1); // Draw new individual trait offsets from each trait's individual-offset distribution - individual->_DrawTraitOffsets(); + individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; @@ -4889,7 +4889,7 @@ bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pe individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent1); // Draw new individual trait offsets from each trait's individual-offset distribution - individual->_DrawTraitOffsets(); + individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; @@ -4975,7 +4975,7 @@ bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pe individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent1); // Draw new individual trait offsets from each trait's individual-offset distribution - individual->_DrawTraitOffsets(); + individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; @@ -5058,7 +5058,7 @@ bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreei individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); // Draw new individual trait offsets from each trait's individual-offset distribution - individual->_DrawTraitOffsets(); + individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; @@ -5260,7 +5260,7 @@ bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreei individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); // Draw new individual trait offsets from each trait's individual-offset distribution - individual->_DrawTraitOffsets(); + individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; @@ -5536,7 +5536,7 @@ bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_ped individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); // Draw new individual trait offsets from each trait's individual-offset distribution - individual->_DrawTraitOffsets(); + individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; @@ -5622,7 +5622,7 @@ bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_ped individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); // Draw new individual trait offsets from each trait's individual-offset distribution - individual->_DrawTraitOffsets(); + individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one Haplosome **haplosomes = individual->haplosomes_; @@ -6468,8 +6468,9 @@ EidosValue_SP Subpopulation::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *Subpopulation::GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size) +EidosValue *Subpopulation::GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -6482,8 +6483,9 @@ EidosValue *Subpopulation::GetProperty_Accelerated_id(EidosObject **p_values, si return int_result; } -EidosValue *Subpopulation::GetProperty_Accelerated_firstMaleIndex(EidosObject **p_values, size_t p_values_size) +EidosValue *Subpopulation::GetProperty_Accelerated_firstMaleIndex(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -6496,8 +6498,9 @@ EidosValue *Subpopulation::GetProperty_Accelerated_firstMaleIndex(EidosObject ** return int_result; } -EidosValue *Subpopulation::GetProperty_Accelerated_individualCount(EidosObject **p_values, size_t p_values_size) +EidosValue *Subpopulation::GetProperty_Accelerated_individualCount(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -6510,8 +6513,9 @@ EidosValue *Subpopulation::GetProperty_Accelerated_individualCount(EidosObject * return int_result; } -EidosValue *Subpopulation::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *Subpopulation::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -6528,8 +6532,9 @@ EidosValue *Subpopulation::GetProperty_Accelerated_tag(EidosObject **p_values, s return int_result; } -EidosValue *Subpopulation::GetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size) +EidosValue *Subpopulation::GetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -6584,8 +6589,9 @@ void Subpopulation::SetProperty(EidosGlobalStringID p_property_id, const EidosVa } } -void Subpopulation::SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Subpopulation::SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) // SLiMCastToUsertagTypeOrRaise() is a no-op at present if (p_source_size == 1) { @@ -6603,8 +6609,9 @@ void Subpopulation::SetProperty_Accelerated_tag(EidosObject **p_values, size_t p } } -void Subpopulation::SetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +void Subpopulation::SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { double source_value = p_source.FloatAtIndex_NOCAST(0, nullptr); diff --git a/core/subpopulation.h b/core/subpopulation.h index e48c996d..7ca8c2a6 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -294,7 +294,7 @@ class Subpopulation : public EidosDictionaryUnretained back->subpopulation_ = this; // Draw new individual trait offsets from each trait's individual-offset distribution - back->_DrawTraitOffsets(); + back->_InitializePerTraitInformation(); return back; } @@ -495,14 +495,14 @@ class Subpopulation : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_configureDisplay(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_firstMaleIndex(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_individualCount(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size); - - static void SetProperty_Accelerated_fitnessScaling(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); - static void SetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_firstMaleIndex(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_individualCount(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + + static void SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); }; diff --git a/core/substitution.cpp b/core/substitution.cpp index 58f51b4f..50789c16 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -203,8 +203,9 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) } } -EidosValue *Substitution::GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -217,8 +218,9 @@ EidosValue *Substitution::GetProperty_Accelerated_id(EidosObject **p_values, siz return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_nucleotide(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_String *string_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_String())->Reserve((int)p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -242,8 +244,9 @@ EidosValue *Substitution::GetProperty_Accelerated_nucleotide(EidosObject **p_val return string_result; } -EidosValue *Substitution::GetProperty_Accelerated_nucleotideValue(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_nucleotideValue(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -260,8 +263,9 @@ EidosValue *Substitution::GetProperty_Accelerated_nucleotideValue(EidosObject ** return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_originTick(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_originTick(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -274,8 +278,9 @@ EidosValue *Substitution::GetProperty_Accelerated_originTick(EidosObject **p_val return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_fixationTick(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_fixationTick(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -288,8 +293,9 @@ EidosValue *Substitution::GetProperty_Accelerated_fixationTick(EidosObject **p_v return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_position(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_position(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -302,8 +308,9 @@ EidosValue *Substitution::GetProperty_Accelerated_position(EidosObject **p_value return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_subpopID(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_subpopID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -316,8 +323,9 @@ EidosValue *Substitution::GetProperty_Accelerated_subpopID(EidosObject **p_value return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -334,8 +342,9 @@ EidosValue *Substitution::GetProperty_Accelerated_tag(EidosObject **p_values, si return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_selectionCoeff(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_selectionCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -348,8 +357,9 @@ EidosValue *Substitution::GetProperty_Accelerated_selectionCoeff(EidosObject **p return float_result; } -EidosValue *Substitution::GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -362,8 +372,9 @@ EidosValue *Substitution::GetProperty_Accelerated_dominanceCoeff(EidosObject **p return float_result; } -EidosValue *Substitution::GetProperty_Accelerated_mutationType(EidosObject **p_values, size_t p_values_size) +EidosValue *Substitution::GetProperty_Accelerated_mutationType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { +#pragma unused (p_property_id) EidosValue_Object *object_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_MutationType_Class))->resize_no_initialize(p_values_size); for (size_t value_index = 0; value_index < p_values_size; ++value_index) diff --git a/core/substitution.h b/core/substitution.h index 7453ac79..0791cbb8 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -82,17 +82,17 @@ class Substitution : public EidosDictionaryRetained virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated_id(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_nucleotide(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_nucleotideValue(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_originTick(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_fixationTick(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_position(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_subpopID(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_tag(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_selectionCoeff(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_mutationType(EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_nucleotideValue(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_originTick(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_fixationTick(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_position(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_subpopID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_selectionCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_mutationType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); }; class Substitution_Class : public EidosDictionaryRetained_Class diff --git a/eidos/eidos_class_TestElement.cpp b/eidos/eidos_class_TestElement.cpp index 5099bf13..6df79bc0 100644 --- a/eidos/eidos_class_TestElement.cpp +++ b/eidos/eidos_class_TestElement.cpp @@ -78,8 +78,9 @@ EidosValue_SP EidosTestElement::GetProperty(EidosGlobalStringID p_property_id) return super::GetProperty(p_property_id); } -EidosValue *EidosTestElement::GetProperty_Accelerated__yolk(EidosObject **p_elements, size_t p_elements_size) +EidosValue *EidosTestElement::GetProperty_Accelerated__yolk(EidosGlobalStringID p_property_id, EidosObject **p_elements, size_t p_elements_size) { +#pragma unused (p_property_id) EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_elements_size); for (size_t element_index = 0; element_index < p_elements_size; ++element_index) @@ -105,8 +106,9 @@ void EidosTestElement::SetProperty(EidosGlobalStringID p_property_id, const Eido return super::SetProperty(p_property_id, p_value); } -void EidosTestElement::SetProperty_Accelerated__yolk(EidosObject **p_elements, size_t p_elements_size, const EidosValue &p_source, size_t p_source_size) +void EidosTestElement::SetProperty_Accelerated__yolk(EidosGlobalStringID p_property_id, EidosObject **p_elements, size_t p_elements_size, const EidosValue &p_source, size_t p_source_size) { +#pragma unused (p_property_id) if (p_source_size == 1) { int64_t source_value = p_source.IntAtIndex_NOCAST(0, nullptr); diff --git a/eidos/eidos_class_TestElement.h b/eidos/eidos_class_TestElement.h index 0e01bff1..ea3ad144 100644 --- a/eidos/eidos_class_TestElement.h +++ b/eidos/eidos_class_TestElement.h @@ -69,8 +69,8 @@ class EidosTestElement : public EidosDictionaryRetained EidosValue_SP ExecuteMethod_squareTest(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism - static EidosValue *GetProperty_Accelerated__yolk(EidosObject **p_elements, size_t p_elements_size); - static void SetProperty_Accelerated__yolk(EidosObject **p_elements, size_t p_elements_size, const EidosValue &p_source, size_t p_source_size); + static EidosValue *GetProperty_Accelerated__yolk(EidosGlobalStringID p_property_id, EidosObject **p_elements, size_t p_elements_size); + static void SetProperty_Accelerated__yolk(EidosGlobalStringID p_property_id, EidosObject **p_elements, size_t p_elements_size, const EidosValue &p_source, size_t p_source_size); }; class EidosTestElement_Class : public EidosDictionaryRetained_Class diff --git a/eidos/eidos_property_signature.h b/eidos/eidos_property_signature.h index b1b56efd..b0a89a85 100644 --- a/eidos/eidos_property_signature.h +++ b/eidos/eidos_property_signature.h @@ -34,7 +34,7 @@ class EidosClass; // vector of property values given a buffer of EidosObjects. The getter is expected to return the correct type for the // property (this is checked). The getter is guaranteed that the EidosObjects are of the correct class; it is allowed to // do a cast of p_values directly to its own type without checking, according to the calling conventions used here. -typedef EidosValue *(*Eidos_AcceleratedPropertyGetter)(EidosObject **p_values, size_t p_values_size); +typedef EidosValue *(*Eidos_AcceleratedPropertyGetter)(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); // This typedef is for an "accelerated property setter". These are static member functions on a class, designed to set a property // value across a buffer of EidosObjects. This is more complex than the getter case, because there are two possibilities: @@ -44,7 +44,7 @@ typedef EidosValue *(*Eidos_AcceleratedPropertyGetter)(EidosObject **p_values, s // to be of the correct class, and may be cast directly. (This is actually guaranteed and checked by the property signature, so if // the signature is declared incorrectly then a mismatch is possible; but that is not the getter/setter's problem to detect.) The // type of p_source is also checked against the signature, and so may be assumed to be of the declared type. -typedef void (*Eidos_AcceleratedPropertySetter)(EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); +typedef void (*Eidos_AcceleratedPropertySetter)(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); class EidosPropertySignature diff --git a/eidos/eidos_value.cpp b/eidos/eidos_value.cpp index 77602090..187c0341 100644 --- a/eidos/eidos_value.cpp +++ b/eidos/eidos_value.cpp @@ -2224,7 +2224,7 @@ EidosValue_SP EidosValue_Object::GetPropertyOfElements(EidosGlobalStringID p_pro { // Accelerated property access is enabled for this property, so the class will do all the work for us // We put this case below the (values_size == 1) case so the accelerated getter can focus on the vectorized case - EidosValue_SP result = EidosValue_SP(signature->accelerated_getter(values_, values_size)); + EidosValue_SP result = EidosValue_SP(signature->accelerated_getter(p_property_id, values_, values_size)); // BCH 4/16/2025: New in SLiM 5, an accelerated getter can return nullptr to say "I don't want to // handle this case, send it down to GetProperty() and do it the slow way", so we fall through. @@ -2322,7 +2322,7 @@ void EidosValue_Object::SetPropertyOfElements(EidosGlobalStringID p_property_id, if (signature->accelerated_set_) { // Accelerated property writing is enabled for this property, so we call the setter directly - signature->accelerated_setter(values_, values_size, p_value, p_value_count); + signature->accelerated_setter(p_property_id, values_, values_size, p_value, p_value_count); } else { @@ -2337,7 +2337,7 @@ void EidosValue_Object::SetPropertyOfElements(EidosGlobalStringID p_property_id, if (signature->accelerated_set_) { // Accelerated property writing is enabled for this property, so we call the setter directly - signature->accelerated_setter(values_, values_size, p_value, p_value_count); + signature->accelerated_setter(p_property_id, values_, values_size, p_value, p_value_count); } else { From 9ba221127ed7dc0aaae00f1f141b37c4cbbe2c61 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 16 Oct 2025 15:05:09 -0400 Subject: [PATCH 016/107] new MutationBlock class for per-species mutation storage --- QtSLiM/QtSLiMAppDelegate.cpp | 2 +- QtSLiM/QtSLiMChromosomeWidget_GL.cpp | 4 +- QtSLiM/QtSLiMChromosomeWidget_QT.cpp | 4 +- QtSLiM/QtSLiMGraphView.cpp | 5 +- QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp | 11 +- QtSLiM/QtSLiMGraphView_1DSampleSFS.cpp | 3 +- QtSLiM/QtSLiMGraphView_2DPopulationSFS.cpp | 3 +- QtSLiM/QtSLiMGraphView_2DSampleSFS.cpp | 3 +- .../QtSLiMGraphView_FrequencyTrajectory.cpp | 3 +- QtSLiM/QtSLiMHaplotypeManager.cpp | 3 +- QtSLiM/QtSLiMWindow.cpp | 6 + SLiM.xcodeproj/project.pbxproj | 14 + SLiMgui/ChromosomeView.mm | 3 +- SLiMgui/GraphView_MutationFrequencySpectra.mm | 9 +- .../GraphView_MutationFrequencyTrajectory.mm | 3 +- SLiMgui/SLiMWindowController.mm | 6 + VERSIONS | 2 + core/chromosome.cpp | 15 +- core/chromosome.h | 1 + core/community.cpp | 47 ++- core/community_eidos.cpp | 1 + core/core.pro | 2 + core/haplosome.cpp | 108 ++++--- core/haplosome.h | 16 +- core/individual.cpp | 40 +-- core/mutation.cpp | 273 +++++----------- core/mutation.h | 95 ++---- core/mutation_block.cpp | 292 ++++++++++++++++++ core/mutation_block.h | 148 +++++++++ core/mutation_run.cpp | 69 ++--- core/mutation_run.h | 46 +-- core/mutation_type.h | 2 + core/population.cpp | 172 +++++++---- core/population.h | 34 +- core/slim_globals.cpp | 6 +- core/slim_globals.h | 5 +- core/species.cpp | 60 +++- core/species.h | 20 +- core/species_eidos.cpp | 11 +- core/subpopulation.cpp | 20 +- core/trait.h | 1 + eidos/eidos_value.cpp | 45 +-- eidos/eidos_value.h | 15 +- 43 files changed, 1010 insertions(+), 618 deletions(-) create mode 100644 core/mutation_block.cpp create mode 100644 core/mutation_block.h diff --git a/QtSLiM/QtSLiMAppDelegate.cpp b/QtSLiM/QtSLiMAppDelegate.cpp index 829b2432..bc0b681d 100644 --- a/QtSLiM/QtSLiMAppDelegate.cpp +++ b/QtSLiM/QtSLiMAppDelegate.cpp @@ -244,7 +244,7 @@ QtSLiMAppDelegate::QtSLiMAppDelegate(QObject *p_parent) : QObject(p_parent) // This lambda (1) ensures that visible top-level windows remain on screen; // (2) raises the active window to the front on the appropriate screen, if appropriate. - auto ensureOnScreen = [this]() { + auto ensureOnScreen = []() { const auto topLevels = QApplication::topLevelWidgets(); QWidget *active = qApp->activeWindow(); for (QWidget *w : topLevels) diff --git a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp index c0e3241f..8dd3a106 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp @@ -30,6 +30,8 @@ #include #include +#include "mutation_block.h" + // // OpenGL-based drawing; maintain this in parallel with the Qt-based drawing! @@ -178,7 +180,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch { int registry_size; const MutationIndex *registry = pop.MutationRegistry(®istry_size); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = displaySpecies->SpeciesMutationBlock()->mutation_buffer_; slim_chromosome_index_t chromosome_index = chromosome->Index(); for (int registry_index = 0; registry_index < registry_size; ++registry_index) diff --git a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp index a5c9baec..a1349368 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp @@ -24,6 +24,8 @@ #include +#include "mutation_block.h" + #include #include #include @@ -174,7 +176,7 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch { int registry_size; const MutationIndex *registry = pop.MutationRegistry(®istry_size); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = displaySpecies->SpeciesMutationBlock()->mutation_buffer_; slim_chromosome_index_t chromosome_index = chromosome->Index(); for (int registry_index = 0; registry_index < registry_size; ++registry_index) diff --git a/QtSLiM/QtSLiMGraphView.cpp b/QtSLiM/QtSLiMGraphView.cpp index f1a99c07..aa1c40ba 100644 --- a/QtSLiM/QtSLiMGraphView.cpp +++ b/QtSLiM/QtSLiMGraphView.cpp @@ -48,6 +48,7 @@ #include "subpopulation.h" #include "haplosome.h" #include "mutation_run.h" +#include "mutation_block.h" QFont QtSLiMGraphView::labelFontOfPointSize(double size) @@ -2551,7 +2552,7 @@ size_t QtSLiMGraphView::tallyGUIMutationReferences(slim_objectid_t subpop_id, in Population &population = graphSpecies->population_; size_t subpop_total_haplosome_count = 0; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = graphSpecies->SpeciesMutationBlock()->mutation_buffer_; { int registry_size; @@ -2618,7 +2619,7 @@ size_t QtSLiMGraphView::tallyGUIMutationReferences(const std::vectorpopulation_; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = graphSpecies->SpeciesMutationBlock()->mutation_buffer_; { int registry_size; diff --git a/QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp b/QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp index 1697bced..94e221fb 100644 --- a/QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp +++ b/QtSLiM/QtSLiMGraphView_1DPopulationSFS.cpp @@ -23,6 +23,8 @@ #include +#include "mutation_block.h" + QtSLiMGraphView_1DPopulationSFS::QtSLiMGraphView_1DPopulationSFS(QWidget *p_parent, QtSLiMWindow *controller) : QtSLiMGraphView(p_parent, controller) { @@ -89,9 +91,10 @@ double *QtSLiMGraphView_1DPopulationSFS::populationSFS(int mutationTypeCount) Population &pop = graphSpecies->population_; pop.TallyMutationReferencesAcrossPopulation(/* p_clock_for_mutrun_experiments */ false); // update tallies; usually this will just use the cache set up by Population::MaintainRegistry() - - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; + + MutationBlock *mutation_block = graphSpecies->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + slim_refcount_t *refcount_block_ptr = mutation_block->refcount_buffer_; int registry_size; const MutationIndex *registry = pop.MutationRegistry(®istry_size); @@ -101,7 +104,7 @@ double *QtSLiMGraphView_1DPopulationSFS::populationSFS(int mutationTypeCount) Chromosome *mut_chromosome = graphSpecies->Chromosomes()[mutation->chromosome_index_]; double totalHaplosomeCount = ((mut_chromosome->total_haplosome_count_ == 0) ? 1 : mut_chromosome->total_haplosome_count_); // prevent a zero count from producing NAN frequencies below - slim_refcount_t mutationRefCount = *(refcount_block_ptr + mutation->BlockIndex()); + slim_refcount_t mutationRefCount = *(refcount_block_ptr + mutation_block->IndexInBlock(mutation)); double mutationFrequency = mutationRefCount / totalHaplosomeCount; int mutationBin = static_cast(floor(mutationFrequency * binCount)); int mutationTypeIndex = mutation->mutation_type_ptr_->mutation_type_index_; diff --git a/QtSLiM/QtSLiMGraphView_1DSampleSFS.cpp b/QtSLiM/QtSLiMGraphView_1DSampleSFS.cpp index fc28f2d7..250c37b2 100644 --- a/QtSLiM/QtSLiMGraphView_1DSampleSFS.cpp +++ b/QtSLiM/QtSLiMGraphView_1DSampleSFS.cpp @@ -33,6 +33,7 @@ #include "QtSLiMWindow.h" #include "subpopulation.h" #include "mutation_type.h" +#include "mutation_block.h" QtSLiMGraphView_1DSampleSFS::QtSLiMGraphView_1DSampleSFS(QWidget *p_parent, QtSLiMWindow *controller) : QtSLiMGraphView(p_parent, controller) @@ -300,7 +301,7 @@ uint64_t *QtSLiMGraphView_1DSampleSFS::mutation1DSFS(void) // Tally into our bins sfs1dbuf_ = static_cast(calloc(histogramBinCount_, sizeof(uint64_t))); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = graphSpecies->SpeciesMutationBlock()->mutation_buffer_; int registry_size; const MutationIndex *registry = population.MutationRegistry(®istry_size); diff --git a/QtSLiM/QtSLiMGraphView_2DPopulationSFS.cpp b/QtSLiM/QtSLiMGraphView_2DPopulationSFS.cpp index 53dffee9..72ed691c 100644 --- a/QtSLiM/QtSLiMGraphView_2DPopulationSFS.cpp +++ b/QtSLiM/QtSLiMGraphView_2DPopulationSFS.cpp @@ -27,6 +27,7 @@ #include #include "mutation_type.h" +#include "mutation_block.h" QtSLiMGraphView_2DPopulationSFS::QtSLiMGraphView_2DPopulationSFS(QWidget *p_parent, QtSLiMWindow *controller) : QtSLiMGraphView(p_parent, controller) @@ -232,7 +233,7 @@ double *QtSLiMGraphView_2DPopulationSFS::mutation2DSFS(void) return nullptr; // Get frequencies in subpop1 and subpop2 - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = graphSpecies->SpeciesMutationBlock()->mutation_buffer_; std::vector refcounts1, refcounts2; size_t subpop1_total_haplosome_count, subpop2_total_haplosome_count; diff --git a/QtSLiM/QtSLiMGraphView_2DSampleSFS.cpp b/QtSLiM/QtSLiMGraphView_2DSampleSFS.cpp index 632a72f1..f3a1c1d2 100644 --- a/QtSLiM/QtSLiMGraphView_2DSampleSFS.cpp +++ b/QtSLiM/QtSLiMGraphView_2DSampleSFS.cpp @@ -33,6 +33,7 @@ #include "QtSLiMWindow.h" #include "subpopulation.h" #include "mutation_type.h" +#include "mutation_block.h" QtSLiMGraphView_2DSampleSFS::QtSLiMGraphView_2DSampleSFS(QWidget *p_parent, QtSLiMWindow *controller) : QtSLiMGraphView(p_parent, controller) @@ -347,7 +348,7 @@ uint64_t *QtSLiMGraphView_2DSampleSFS::mutation2DSFS(void) int registry_size; const MutationIndex *registry = population.MutationRegistry(®istry_size); const MutationIndex *registry_iter_end = registry + registry_size; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = graphSpecies->SpeciesMutationBlock()->mutation_buffer_; // Find our subpops and mutation type Subpopulation *subpop1 = graphSpecies->SubpopulationWithID(selectedSubpopulation1ID_); diff --git a/QtSLiM/QtSLiMGraphView_FrequencyTrajectory.cpp b/QtSLiM/QtSLiMGraphView_FrequencyTrajectory.cpp index c6722529..6bd9ed89 100644 --- a/QtSLiM/QtSLiMGraphView_FrequencyTrajectory.cpp +++ b/QtSLiM/QtSLiMGraphView_FrequencyTrajectory.cpp @@ -30,6 +30,7 @@ #include "QtSLiMWindow.h" #include "subpopulation.h" +#include "mutation_block.h" QtSLiMGraphView_FrequencyTrajectory::QtSLiMGraphView_FrequencyTrajectory(QWidget *p_parent, QtSLiMWindow *controller) : QtSLiMGraphView(p_parent, controller) @@ -137,7 +138,7 @@ void QtSLiMGraphView_FrequencyTrajectory::fetchDataForFinishedTick(void) subpop_total_haplosome_count = 1; // refcounts will all be zero; prevent NAN values below, make them 0 instead // Now we can run through the mutations and use the tallies in gui_scratch_reference_count to update our histories - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = graphSpecies->SpeciesMutationBlock()->mutation_buffer_; for (const MutationIndex *registry_iter = registry; registry_iter != registry_iter_end; ++registry_iter) { diff --git a/QtSLiM/QtSLiMHaplotypeManager.cpp b/QtSLiM/QtSLiMHaplotypeManager.cpp index a7e9f140..eeed3b14 100644 --- a/QtSLiM/QtSLiMHaplotypeManager.cpp +++ b/QtSLiM/QtSLiMHaplotypeManager.cpp @@ -46,6 +46,7 @@ #include "eidos_globals.h" #include "subpopulation.h" #include "species.h" +#include "mutation_block.h" const int QtSLiM_SubpopulationStripWidth = 5; @@ -480,7 +481,7 @@ void QtSLiMHaplotypeManager::configureMutationInfoBuffer(Chromosome *chromosome) mutationPositions = static_cast(malloc(sizeof(slim_position_t) * mutationIndexCount)); // Copy the information we need on each mutation in use - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = graphSpecies->SpeciesMutationBlock()->mutation_buffer_; for (const MutationIndex *reg_ptr = registry; reg_ptr != reg_end_ptr; ++reg_ptr) { diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index bebb809a..79604d82 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -4406,6 +4406,12 @@ void QtSLiMWindow::displayProfileResults(void) tc.insertText(attributedStringForByteCount(mem_last_C.mutationRefcountBuffer, final_total, colored_menlo), colored_menlo); tc.insertText(" : refcount buffer\n", optima13_d); + tc.insertText(" ", menlo11_d); + tc.insertText(attributedStringForByteCount(mem_tot_C.mutationPerTraitBuffer / div, average_total, colored_menlo), colored_menlo); + tc.insertText(" / ", optima13_d); + tc.insertText(attributedStringForByteCount(mem_last_C.mutationPerTraitBuffer, final_total, colored_menlo), colored_menlo); + tc.insertText(" : per-trait buffer\n", optima13_d); + tc.insertText(" ", menlo11_d); tc.insertText(attributedStringForByteCount(mem_tot_C.mutationUnusedPoolSpace / div, average_total, colored_menlo), colored_menlo); tc.insertText(" / ", optima13_d); diff --git a/SLiM.xcodeproj/project.pbxproj b/SLiM.xcodeproj/project.pbxproj index 4da25ff4..3fcdeb62 100644 --- a/SLiM.xcodeproj/project.pbxproj +++ b/SLiM.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 9801EEB72E9F28A10017C779 /* mutation_block.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9801EEB62E9F28980017C779 /* mutation_block.cpp */; }; + 9801EEB82E9F28A10017C779 /* mutation_block.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9801EEB62E9F28980017C779 /* mutation_block.cpp */; }; + 9801EEB92E9F28A10017C779 /* mutation_block.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9801EEB62E9F28980017C779 /* mutation_block.cpp */; }; + 9801EEBA2E9F28A10017C779 /* mutation_block.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9801EEB62E9F28980017C779 /* mutation_block.cpp */; }; + 9801EEBB2E9F28A10017C779 /* mutation_block.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9801EEB62E9F28980017C779 /* mutation_block.cpp */; }; 98024742215D85880025D29C /* FindRecipePanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98024740215D85880025D29C /* FindRecipePanel.xib */; }; 980566E225A7C5B9008D3C7F /* fdist.c in Sources */ = {isa = PBXBuildFile; fileRef = 980566E125A7C5B9008D3C7F /* fdist.c */; }; 980566E325A7C5B9008D3C7F /* fdist.c in Sources */ = {isa = PBXBuildFile; fileRef = 980566E125A7C5B9008D3C7F /* fdist.c */; }; @@ -1747,6 +1752,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 9801EEB52E9F28840017C779 /* mutation_block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mutation_block.h; sourceTree = ""; }; + 9801EEB62E9F28980017C779 /* mutation_block.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mutation_block.cpp; sourceTree = ""; }; 98024741215D85880025D29C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/FindRecipePanel.xib; sourceTree = ""; }; 980566E125A7C5B9008D3C7F /* fdist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fdist.c; sourceTree = ""; }; 98076602244934A800F6CBB4 /* zutil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zutil.h; sourceTree = ""; }; @@ -2535,6 +2542,8 @@ 98E9A6A61A3CD5A0000AD4FC /* haplosome.cpp */, 98606AED1DED0DCD00821CFF /* mutation_run.h */, 98606AEC1DED0DCD00821CFF /* mutation_run.cpp */, + 9801EEB52E9F28840017C779 /* mutation_block.h */, + 9801EEB62E9F28980017C779 /* mutation_block.cpp */, 98E9A6891A3CCFD0000AD4FC /* mutation.h */, 98E9A6881A3CCFD0000AD4FC /* mutation.cpp */, 98E9A69E1A3CD551000AD4FC /* substitution.h */, @@ -3928,6 +3937,7 @@ 98606AEE1DED0DCD00821CFF /* mutation_run.cpp in Sources */, 98C821241C7A980000548839 /* inline.c in Sources */, 98CEFD6C2AAFABAA00D2C9B4 /* inline.c in Sources */, + 9801EEB72E9F28A10017C779 /* mutation_block.cpp in Sources */, 98DE4C131B6F9657004FDF5F /* eidos_property_signature.cpp in Sources */, 98076614244934A800F6CBB4 /* compress.c in Sources */, 98E9A6961A3CD51A000AD4FC /* genomic_element_type.cpp in Sources */, @@ -4105,6 +4115,7 @@ 9861521B2B167AED0083E68F /* species.cpp in Sources */, 986152162B167AED0083E68F /* spatial_kernel.cpp in Sources */, 986152562B167B4E0083E68F /* coerce.c in Sources */, + 9801EEBB2E9F28A10017C779 /* mutation_block.cpp in Sources */, 986152592B167B4E0083E68F /* init.c in Sources */, 986152662B167B4E0083E68F /* init.c in Sources */, 9861524A2B167B4E0083E68F /* ddot.c in Sources */, @@ -4484,6 +4495,7 @@ 98CF527F294A3FC900557BBA /* GraphView_MutationFixationTimeHistogram.mm in Sources */, 98CF5280294A3FC900557BBA /* eidos_functions_files.cpp in Sources */, 98CF5281294A3FC900557BBA /* rowcol.c in Sources */, + 9801EEBA2E9F28A10017C779 /* mutation_block.cpp in Sources */, 98CF5282294A3FC900557BBA /* gausszig.c in Sources */, 98CF5283294A3FC900557BBA /* eidos_type_interpreter.cpp in Sources */, 98D7D6672AB24CBC002AFE34 /* chisq.c in Sources */, @@ -4836,6 +4848,7 @@ 986926E51AA3FF000000E138 /* GraphView_MutationFixationTimeHistogram.mm in Sources */, 981DC35128E26F8B000ABE91 /* eidos_functions_files.cpp in Sources */, 98332AED1FDBC29400274FF0 /* rowcol.c in Sources */, + 9801EEB92E9F28A10017C779 /* mutation_block.cpp in Sources */, 98C8214D1C7A983700548839 /* gausszig.c in Sources */, 9893C7EA1CDFE9870029AC94 /* eidos_type_interpreter.cpp in Sources */, 98D7D6662AB24CBC002AFE34 /* chisq.c in Sources */, @@ -5125,6 +5138,7 @@ 98D7ECEC28CE58FC00DEAAC4 /* GitSHA1_Xcode.cpp in Sources */, 98D7ECED28CE58FC00DEAAC4 /* eidos_class_Object.cpp in Sources */, 98CEFD732AAFABAA00D2C9B4 /* inline.c in Sources */, + 9801EEB82E9F28A10017C779 /* mutation_block.cpp in Sources */, 98D7ECEE28CE58FC00DEAAC4 /* lodepng.cpp in Sources */, 98D7ECEF28CE58FC00DEAAC4 /* mutation_run.cpp in Sources */, 98D7ECF028CE58FC00DEAAC4 /* inline.c in Sources */, diff --git a/SLiMgui/ChromosomeView.mm b/SLiMgui/ChromosomeView.mm index 05f9dcfd..cc0f5d2c 100644 --- a/SLiMgui/ChromosomeView.mm +++ b/SLiMgui/ChromosomeView.mm @@ -23,6 +23,7 @@ #import "CocoaExtra.h" #include "community.h" +#include "mutation_block.h" NSString *SLiMChromosomeSelectionChangedNotification = @"SLiMChromosomeSelectionChangedNotification"; @@ -639,7 +640,7 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome double totalHaplosomeCount = chromosome->gui_total_haplosome_count_; // this includes only haplosomes in the selected subpopulations int registry_size; const MutationIndex *registry = pop.MutationRegistry(®istry_size); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = displaySpecies->SpeciesMutationBlock()->mutation_buffer_; if ((registry_size < 1000) || (displayedRange.length < interiorRect.size.width)) { diff --git a/SLiMgui/GraphView_MutationFrequencySpectra.mm b/SLiMgui/GraphView_MutationFrequencySpectra.mm index e287e121..99a947f1 100644 --- a/SLiMgui/GraphView_MutationFrequencySpectra.mm +++ b/SLiMgui/GraphView_MutationFrequencySpectra.mm @@ -21,6 +21,8 @@ #import "GraphView_MutationFrequencySpectra.h" #import "SLiMWindowController.h" +#import "mutation_block.h" + @implementation GraphView_MutationFrequencySpectra @@ -79,8 +81,9 @@ - (double *)mutationFrequencySpectrumWithController:(SLiMWindowController *)cont pop.TallyMutationReferencesAcrossPopulation(/* p_clock_for_mutrun_experiments */ false); // update tallies; usually this will just use the cache set up by Population::MaintainMutationRegistry() - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; + MutationBlock *mutation_block = displaySpecies->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + slim_refcount_t *refcount_block_ptr = mutation_block->refcount_buffer_; int registry_size; const MutationIndex *registry = pop.MutationRegistry(®istry_size); @@ -89,7 +92,7 @@ - (double *)mutationFrequencySpectrumWithController:(SLiMWindowController *)cont const Mutation *mutation = mut_block_ptr + registry[registry_index]; Chromosome *mut_chromosome = displaySpecies->Chromosomes()[mutation->chromosome_index_]; - slim_refcount_t mutationRefCount = *(refcount_block_ptr + mutation->BlockIndex()); + slim_refcount_t mutationRefCount = *(refcount_block_ptr + mutation_block->IndexInBlock(mutation)); double mutationFrequency = mutationRefCount / mut_chromosome->total_haplosome_count_; int mutationBin = (int)floor(mutationFrequency * binCount); int mutationTypeIndex = mutation->mutation_type_ptr_->mutation_type_index_; diff --git a/SLiMgui/GraphView_MutationFrequencyTrajectory.mm b/SLiMgui/GraphView_MutationFrequencyTrajectory.mm index 3e27932a..37e5a44a 100644 --- a/SLiMgui/GraphView_MutationFrequencyTrajectory.mm +++ b/SLiMgui/GraphView_MutationFrequencyTrajectory.mm @@ -22,6 +22,7 @@ #import "SLiMWindowController.h" #include "community.h" +#include "mutation_block.h" @implementation GraphView_MutationFrequencyTrajectory @@ -304,7 +305,7 @@ - (void)fetchDataForFinishedTick int haplosome_count_per_individual = displaySpecies->HaplosomeCountPerIndividual(); int subpop_total_haplosome_count = 0; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = displaySpecies->SpeciesMutationBlock()->mutation_buffer_; const MutationIndex *registry_iter = registry; const MutationIndex *registry_iter_end = registry + registry_size; diff --git a/SLiMgui/SLiMWindowController.mm b/SLiMgui/SLiMWindowController.mm index 5a8567ca..4df981d7 100644 --- a/SLiMgui/SLiMWindowController.mm +++ b/SLiMgui/SLiMWindowController.mm @@ -2232,6 +2232,12 @@ - (void)displayProfileResults [content appendAttributedString:[NSAttributedString attributedStringForByteCount:mem_last_C.mutationRefcountBuffer total:final_total attributes:menlo11_d]]; [content eidosAppendString:@" : refcount buffer\n" attributes:optima13_d]; + [content eidosAppendString:@" " attributes:menlo11_d]; + [content appendAttributedString:[NSAttributedString attributedStringForByteCount:mem_tot_C.mutationPerTraitBuffer / div total:average_total attributes:menlo11_d]]; + [content eidosAppendString:@" / " attributes:optima13_d]; + [content appendAttributedString:[NSAttributedString attributedStringForByteCount:mem_last_C.mutationPerTraitBuffer total:final_total attributes:menlo11_d]]; + [content eidosAppendString:@" : per-trait buffer\n" attributes:optima13_d]; + [content eidosAppendString:@" " attributes:menlo11_d]; [content appendAttributedString:[NSAttributedString attributedStringForByteCount:mem_tot_C.mutationUnusedPoolSpace / div total:average_total attributes:menlo11_d]]; [content eidosAppendString:@" / " attributes:optima13_d]; diff --git a/VERSIONS b/VERSIONS index 33842a03..cb6fc2d1 100644 --- a/VERSIONS +++ b/VERSIONS @@ -57,6 +57,8 @@ multitrait branch: add Individual properties for each trait in the individual's species, allowing direct access to the phenotype for each trait in an individual add Species properties for each trait in the species, allowing direct access to traits in this species make code completion work for the new dynamic properties on Species and Individual generated by initializeTrait() + shift from a single global mutation block into per-species mutation blocks, and make a new C++ class, MutationBlock, to encapsulate this + this is a forced move because we want the mutation block to have a separate buffer of per-trait state for mutations, and the number of traits varies among species version 5.1 (Eidos version 4.1): diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 9c7efdb9..7f66b066 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -27,6 +27,7 @@ #include "species.h" #include "individual.h" #include "subpopulation.h" +#include "mutation_block.h" #include #include @@ -1038,10 +1039,11 @@ MutationIndex Chromosome::DrawNewMutation(std::pair(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE, SINCE WE DO NOT KNOW WHAT HAPLOSOME WE WILL BE INSERTED INTO! THIS IS THE CALLER'S RESPONSIBILITY! - MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); + MutationBlock *mutation_block = mutation_block_; + MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); // A nucleotide value of -1 is always used here; in nucleotide-based models this gets patched later, but that is sequence-dependent and background-dependent - Mutation *mutation = gSLiM_Mutation_Block + new_mut_index; + Mutation *mutation = mutation_block->mutation_buffer_ + new_mut_index; new (mutation) Mutation(mutation_type_ptr, index_, p_position.first, selection_coeff, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, p_subpop_index, p_tick, -1); // FIXME MULTITRAIT @@ -1408,15 +1410,16 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pair(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE! THIS IS THE CALLER'S RESPONSIBILITY! - MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); - Mutation *mutation = gSLiM_Mutation_Block + new_mut_index; + MutationBlock *mutation_block = mutation_block_; + MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); + Mutation *mutation = mutation_block->mutation_buffer_ + new_mut_index; new (mutation) Mutation(mutation_type_ptr, index_, position, selection_coeff, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, p_subpop_index, p_tick, nucleotide); // FIXME MULTITRAIT // Call mutation() callbacks if there are any if (p_mutation_callbacks) { - Mutation *post_callback_mut = ApplyMutationCallbacks(gSLiM_Mutation_Block + new_mut_index, background_haplosome, &source_element, original_nucleotide, *p_mutation_callbacks); + Mutation *post_callback_mut = ApplyMutationCallbacks(mutation, background_haplosome, &source_element, original_nucleotide, *p_mutation_callbacks); // If the callback didn't return the proposed mutation, it will not be used; dispose of it if (post_callback_mut != mutation) @@ -1434,7 +1437,7 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pairBlockIndex(); + MutationIndex post_callback_mut_index = mutation_block->IndexInBlock(post_callback_mut); if (new_mut_index != post_callback_mut_index) { diff --git a/core/chromosome.h b/core/chromosome.h index 575093db..d81fa4d9 100644 --- a/core/chromosome.h +++ b/core/chromosome.h @@ -198,6 +198,7 @@ class Chromosome : public EidosDictionaryRetained Community &community_; Species &species_; + MutationBlock *mutation_block_ = nullptr; // NOT OWNED; a pointer to the MutationBlock from the species // the total haplosome count depends on the chromosome; it will be different for an autosome versus a sex chromosome, for example slim_refcount_t total_haplosome_count_ = 0; // the number of non-null haplosomes in the population; a fixed mutation has this count diff --git a/core/community.cpp b/core/community.cpp index 3c6e1811..a9292486 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -30,6 +30,7 @@ #include "polymorphism.h" #include "subpopulation.h" #include "interaction_type.h" +#include "mutation_block.h" #include "log_file.h" #include @@ -2546,23 +2547,28 @@ void Community::AllSpecies_CheckIntegrity(void) const MutationIndex *registry = species->population_.MutationRegistry(®istry_size); std::vector indices; - for (int registry_index = 0; registry_index < registry_size; ++registry_index) + if (registry_size) { - MutationIndex mutation_index = registry[registry_index]; + MutationIndex mutBlockCapacity = species->SpeciesMutationBlock()->capacity_; - if ((mutation_index < 0) || (mutation_index >= gSLiM_Mutation_Block_Capacity)) - EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) mutation index " << mutation_index << " out of the mutation block." << EidosTerminate(); + for (int registry_index = 0; registry_index < registry_size; ++registry_index) + { + MutationIndex mutation_index = registry[registry_index]; + + if ((mutation_index < 0) || (mutation_index >= mutBlockCapacity)) + EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) mutation index " << mutation_index << " out of the mutation block." << EidosTerminate(); + + indices.push_back(mutation_index); + } - indices.push_back(mutation_index); + size_t original_size = indices.size(); + + std::sort(indices.begin(), indices.end()); + indices.resize(static_cast(std::distance(indices.begin(), std::unique(indices.begin(), indices.end())))); + + if (indices.size() != original_size) + EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) duplicate mutation index in the mutation registry (size difference " << (original_size - indices.size()) << ")." << EidosTerminate(); } - - size_t original_size = indices.size(); - - std::sort(indices.begin(), indices.end()); - indices.resize(static_cast(std::distance(indices.begin(), std::unique(indices.begin(), indices.end())))); - - if (indices.size() != original_size) - EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) duplicate mutation index in the mutation registry (size difference " << (original_size - indices.size()) << ")." << EidosTerminate(); } #endif } @@ -3393,8 +3399,19 @@ void Community::TabulateSLiMMemoryUsage_Community(SLiMMemoryUsage_Community *p_u p_usage->communityObjects = p_usage->communityObjects_count * sizeof(Community); // Mutation global buffers - p_usage->mutationRefcountBuffer = SLiMMemoryUsageForMutationRefcounts(); - p_usage->mutationUnusedPoolSpace = SLiMMemoryUsageForFreeMutations(); // note that in SLiMgui everybody shares this + // FIXME MULTITRAIT need to shift these memory usage metrics down to the species level + p_usage->mutationRefcountBuffer = 0.0; + p_usage->mutationPerTraitBuffer = 0.0; + p_usage->mutationUnusedPoolSpace = 0.0; + + for (Species *species : all_species_) + { + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + + p_usage->mutationRefcountBuffer += mutation_block->MemoryUsageForMutationRefcounts(); + p_usage->mutationPerTraitBuffer += mutation_block->MemoryUsageForTraitInfo(); + p_usage->mutationUnusedPoolSpace += mutation_block->MemoryUsageForFreeMutations(); + } // InteractionType { diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index f00d074a..0abc3185 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -984,6 +984,7 @@ EidosValue_SP Community::ExecuteMethod_outputUsage(EidosGlobalStringID p_method_ // Mutation out << " Mutation objects (" << usage_all_species.mutationObjects_count << "): " << PrintBytes(usage_all_species.mutationObjects) << std::endl; out << " Refcount buffer: " << PrintBytes(usage_community.mutationRefcountBuffer) << std::endl; + out << " Per-trait buffer: " << PrintBytes(usage_community.mutationPerTraitBuffer) << std::endl; out << " Unused pool space: " << PrintBytes(usage_community.mutationUnusedPoolSpace) << std::endl; // MutationRun diff --git a/core/core.pro b/core/core.pro index 7759731d..97affca4 100644 --- a/core/core.pro +++ b/core/core.pro @@ -95,6 +95,7 @@ SOURCES += \ individual.cpp \ interaction_type.cpp \ log_file.cpp \ + mutation_block.cpp \ mutation_run.cpp \ mutation_type.cpp \ mutation.cpp \ @@ -125,6 +126,7 @@ HEADERS += \ individual.h \ interaction_type.h \ log_file.h \ + mutation_block.h \ mutation_run.h \ mutation_type.h \ mutation.h \ diff --git a/core/haplosome.cpp b/core/haplosome.cpp index f667169a..e44ff566 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -26,6 +26,7 @@ #include "species.h" #include "polymorphism.h" #include "subpopulation.h" +#include "mutation_block.h" #include "eidos_sorting.h" #include @@ -334,7 +335,7 @@ void Haplosome::record_derived_states(Species *p_species) const // This is called by Species::RecordAllDerivedStatesFromSLiM() to record all the derived states present // in a given haplosome that was just created by readFromPopulationFile() or some similar situation. It should // make calls to record the derived state at each position in the haplosome that has any mutation. - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = p_species->SpeciesMutationBlock()->mutation_buffer_; THREAD_SAFETY_IN_ACTIVE_PARALLEL("Haplosome::record_derived_states(): usage of statics"); @@ -445,7 +446,7 @@ EidosValue_SP Haplosome::GetProperty(EidosGlobalStringID p_property_id) if (IsDeferred()) EIDOS_TERMINATION << "ERROR (Haplosome::GetProperty): the mutations of deferred haplosomes cannot be accessed." << EidosTerminate(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = individual_->subpopulation_->species_.SpeciesMutationBlock()->mutation_buffer_; int mut_count = mutation_count(); EidosValue_Object *vec = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Mutation_Class))->resize_no_initialize_RR(mut_count); EidosValue_SP result_SP = EidosValue_SP(vec); @@ -852,7 +853,7 @@ EidosValue_SP Haplosome::ExecuteMethod_Accelerated_countOfMutationsOfType(EidosO // Count the number of mutations of the given type const int32_t mutrun_count = ((Haplosome *)(p_elements[0]))->mutrun_count_; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species->SpeciesMutationBlock()->mutation_buffer_; EidosValue_Int *integer_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_elements_size); bool saw_error = false; @@ -907,7 +908,7 @@ EidosValue_SP Haplosome::ExecuteMethod_mutationsOfType(EidosGlobalStringID p_met // We want to return a singleton if we can, but we also want to avoid scanning through all our mutations twice. // We do this by not creating a vector until we see the second match; with one match, we make a singleton. - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; Mutation *first_match = nullptr; EidosValue_Object *vec = nullptr; EidosValue_SP result_SP; @@ -1263,7 +1264,7 @@ EidosValue_SP Haplosome::ExecuteMethod_positionsOfMutationsOfType(EidosGlobalStr // Return the positions of mutations of the given type EidosValue_Int *int_result = new (gEidosValuePool->AllocateChunk()) EidosValue_Int(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; for (int run_index = 0; run_index < mutrun_count_; ++run_index) { @@ -1299,7 +1300,7 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species.community_, &species, "sumOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK // Sum the selection coefficients of mutations of the given type - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; double selcoeff_sum = 0.0; int mutrun_count = mutrun_count_; @@ -1322,9 +1323,9 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID } // print the sample represented by haplosomes, using SLiM's own format -void Haplosome::PrintHaplosomes_SLiM(std::ostream &p_out, std::vector &p_haplosomes, bool p_output_object_tags) +void Haplosome::PrintHaplosomes_SLiM(std::ostream &p_out, Species &p_species, std::vector &p_haplosomes, bool p_output_object_tags) { - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = p_species.SpeciesMutationBlock()->mutation_buffer_; slim_popsize_t sample_size = (slim_popsize_t)p_haplosomes.size(); // get the polymorphisms within the sample @@ -1416,9 +1417,9 @@ void Haplosome::PrintHaplosomes_SLiM(std::ostream &p_out, std::vector &p_haplosomes, const Chromosome &p_chromosome, bool p_filter_monomorphic) +void Haplosome::PrintHaplosomes_MS(std::ostream &p_out, Species &p_species, std::vector &p_haplosomes, const Chromosome &p_chromosome, bool p_filter_monomorphic) { - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = p_species.SpeciesMutationBlock()->mutation_buffer_; slim_popsize_t sample_size = (slim_popsize_t)p_haplosomes.size(); // BCH 7 Nov. 2016: sort the polymorphisms by position since that is the expected sort @@ -1686,7 +1687,7 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i Species &species = p_chromosome.species_; bool nucleotide_based = species.IsNucleotideBased(); NucleotideArray *ancestral_seq = p_chromosome.AncestralSequence(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; int64_t individual_count; // if groupAsIndividuals is false, we just act as though the chromosome is haploid @@ -2282,6 +2283,8 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addMutations(EidosGlobalStringID p_ species->population_.CheckForDeferralInHaplosomes(p_target, "Haplosome_Class::ExecuteMethod_addMutations"); Community &community = species->community_; + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; // All haplosomes must belong to the same chromosome, and all mutations being added must belong to that chromosome too. // It's important that a mismatch result in an error; attempts to add mutations to chromosomes inconsistently should be flagged. @@ -2515,9 +2518,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addMutations(EidosGlobalStringID p_ if (add_pos / mutrun_length != mutrun_index) break; - if (target_run->enforce_stack_policy_for_addition(mut_to_add->position_, mut_to_add->mutation_type_ptr_)) + if (target_run->enforce_stack_policy_for_addition(mut_block_ptr, mut_to_add->position_, mut_to_add->mutation_type_ptr_)) { - target_run->insert_sorted_mutation_if_unique(mut_to_add->BlockIndex()); + target_run->insert_sorted_mutation_if_unique(mut_block_ptr, mutation_block->IndexInBlock(mut_to_add)); // No need to add the mutation to the registry; how would the user ever get a Mutation that was not already in it? // Similarly, no need to check and set pure_neutral_ and all_pure_neutral_DFE_; the mutation is already in the system @@ -2547,7 +2550,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addMutations(EidosGlobalStringID p_ std::vector &haplosome_positions = haplosome_pair.second; for (slim_position_t position : haplosome_positions) - species->RecordNewDerivedState(target_haplosome, position, *target_haplosome->derived_mutation_ids_at_position(position)); + species->RecordNewDerivedState(target_haplosome, position, *target_haplosome->derived_mutation_ids_at_position(mut_block_ptr, position)); } } @@ -2588,6 +2591,8 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID species->population_.CheckForDeferralInHaplosomes(p_target, "Haplosome_Class::ExecuteMethod_addNewMutation"); Community &community = species->community_; + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; // All haplosomes must belong to the same chromosome. It's important that a mismatch result in an error; // attempts to add mutations to chromosomes inconsistently should be flagged. @@ -2900,9 +2905,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID nucleotide = nucleotide_lookup[(unsigned char)(arg_nucleotide->StringAtIndex_NOCAST(mut_parameter_index, nullptr)[0])]; } - MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); + MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, origin_subpop_id, origin_tick, (int8_t)nucleotide); // FIXME MULTITRAIT + Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, origin_subpop_id, origin_tick, (int8_t)nucleotide); // FIXME MULTITRAIT // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DFE), so we set all_pure_neutral_DFE_ also @@ -2923,11 +2928,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // BCH 18 January 2020: If a vector of positions was provided, mutations_to_add might be out of sorted // order, which is expected below by clear_set_and_merge(), so we sort here if ((position_count != 1) && (mutations_to_add.size() > 1)) - { - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - std::sort(mutations_to_add.begin(), mutations_to_add.end(), [mut_block_ptr](MutationIndex i1, MutationIndex i2) {return (mut_block_ptr + i1)->position_ < (mut_block_ptr + i2)->position_;}); - } // Now start the bulk operation and add mutations_to_add to every target haplosome Haplosome::BulkOperationStart(operation_id, mutrun_index); @@ -2945,7 +2946,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID if (modifiable_mutrun) { // We merge the original run (which has not yet been freed!) and mutations_to_add into modifiable_mutrun - modifiable_mutrun->clear_set_and_merge(*original_run, mutations_to_add); + modifiable_mutrun->clear_set_and_merge(mut_block_ptr, *original_run, mutations_to_add); } // TREE SEQUENCE RECORDING @@ -2960,12 +2961,12 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID while (muts != muts_end) { - Mutation *mut = gSLiM_Mutation_Block + *(muts++); + Mutation *mut = mut_block_ptr + *(muts++); slim_position_t pos = mut->position_; if (pos != previous_position) { - species->RecordNewDerivedState(target_haplosome, pos, *target_haplosome->derived_mutation_ids_at_position(pos)); + species->RecordNewDerivedState(target_haplosome, pos, *target_haplosome->derived_mutation_ids_at_position(mut_block_ptr, pos)); previous_position = pos; } } @@ -3163,9 +3164,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_outputX(EidosGlobalStringID p_metho // Call out to print the actual sample if (p_method_id == gID_outputHaplosomes) - Haplosome::PrintHaplosomes_SLiM(output_stream, haplosomes, output_object_tags); + Haplosome::PrintHaplosomes_SLiM(output_stream, *species, haplosomes, output_object_tags); else if (p_method_id == gID_outputHaplosomesToMS) - Haplosome::PrintHaplosomes_MS(output_stream, haplosomes, *chromosome, filter_monomorphic); + Haplosome::PrintHaplosomes_MS(output_stream, *species, haplosomes, *chromosome, filter_monomorphic); else if (p_method_id == gID_outputHaplosomesToVCF) Haplosome::PrintHaplosomes_VCF(output_stream, haplosomes, *chromosome, group_as_individuals, output_multiallelics, simplify_nucs, output_nonnucs); } @@ -3198,10 +3199,10 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_outputX(EidosGlobalStringID p_metho outfile << " " << outfile_path << std::endl; - Haplosome::PrintHaplosomes_SLiM(outfile, haplosomes, output_object_tags); + Haplosome::PrintHaplosomes_SLiM(outfile, *species, haplosomes, output_object_tags); break; case gID_outputHaplosomesToMS: - Haplosome::PrintHaplosomes_MS(outfile, haplosomes, *chromosome, filter_monomorphic); + Haplosome::PrintHaplosomes_MS(outfile, *species, haplosomes, *chromosome, filter_monomorphic); break; case gID_outputHaplosomesToVCF: Haplosome::PrintHaplosomes_VCF(outfile, haplosomes, *chromosome, group_as_individuals, output_multiallelics, simplify_nucs, output_nonnucs); @@ -3259,6 +3260,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr species.population_.CheckForDeferralInHaplosomes(p_target, "Haplosome_Class::ExecuteMethod_readHaplosomesFromMS"); + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + // For MS input, we need to know the chromosome to calculate positions from the normalized interval [0, 1]. // We infer it from the haplosomes, and in a multi-chromosome species all the haplosomes must belong to it. Haplosome * const *targets_data = (Haplosome * const *)p_target->ObjectData(); @@ -3401,9 +3405,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr nucleotide++; } - MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); + MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, subpop_index, origin_tick, nucleotide); // FIXME MULTITRAIT + Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, subpop_index, origin_tick, nucleotide); // FIXME MULTITRAIT // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ if (selection_coeff != 0.0) @@ -3420,7 +3424,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr } // Sort the mutations by position so we can add them in order, and make an "order" vector for accessing calls in the sorted order - Mutation *mut_block_ptr = gSLiM_Mutation_Block; std::vector order_vec = EidosSortIndexes(positions); std::sort(mutation_indices.begin(), mutation_indices.end(), [mut_block_ptr](MutationIndex i1, MutationIndex i2) {return (mut_block_ptr + i1)->position_ < (mut_block_ptr + i2)->position_;}); @@ -3473,10 +3476,10 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr if (haplosome_started_empty) current_mutrun->emplace_back(mut_index); else - current_mutrun->insert_sorted_mutation(mut_index); + current_mutrun->insert_sorted_mutation(mut_block_ptr, mut_index); if (recording_mutations) - species.RecordNewDerivedState(haplosome, mut_pos, *haplosome->derived_mutation_ids_at_position(mut_pos)); + species.RecordNewDerivedState(haplosome, mut_pos, *haplosome->derived_mutation_ids_at_position(mut_block_ptr, mut_pos)); } } } @@ -3513,6 +3516,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt species->population_.CheckForDeferralInHaplosomes(p_target, "Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF"); + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + // All haplosomes must belong to the same chromosome, and in multichrom models the CHROM field must match its symbol const std::vector &chromosomes = species->Chromosomes(); bool model_is_multi_chromosome = (chromosomes.size() > 1); @@ -4042,7 +4048,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt } // instantiate the mutation with the values decided upon - MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); + MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); Mutation *new_mut; if (info_mutids.size() > 0) @@ -4050,12 +4056,12 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt // a mutation ID was supplied; we use it blindly, having checked above that we are in the case where this is legal slim_mutationid_t mut_mutid = info_mutids[alt_allele_index]; - new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mut_mutid, mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); + new_mut = new (mut_block_ptr + new_mut_index) Mutation(mut_mutid, mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } else { // no mutation ID supplied, so use whatever is next - new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); + new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ @@ -4103,16 +4109,15 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt if (all_target_haplosomes_started_empty) haplosome_last_mutrun->emplace_back(mut_index); else - haplosome_last_mutrun->insert_sorted_mutation(mut_index); + haplosome_last_mutrun->insert_sorted_mutation(mut_block_ptr, mut_index); if (recording_mutations) - species->RecordNewDerivedState(haplosome, mut_position, *haplosome->derived_mutation_ids_at_position(mut_position)); + species->RecordNewDerivedState(haplosome, mut_position, *haplosome->derived_mutation_ids_at_position(mut_block_ptr, mut_position)); } } } // Return the instantiated mutations - Mutation *mut_block_ptr = gSLiM_Mutation_Block; int mutation_count = (int)mutation_indices.size(); EidosValue_Object *vec = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Mutation_Class))->resize_no_initialize_RR(mutation_count); @@ -4130,7 +4135,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID EidosValue *mutations_value = p_arguments[0].get(); EidosValue *substitute_value = p_arguments[1].get(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; int target_size = p_target->Count(); if (target_size == 0) @@ -4142,6 +4146,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID if (!species) EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_removeMutations): removeMutations() requires that all target haplosomes belong to the same species." << EidosTerminate(); + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + // All haplosomes must belong to the same chromosome, and all mutations being added must belong to that chromosome too. // It's important that a mismatch result in an error; attempts to add mutations to chromosomes inconsistently should be flagged. int mutations_count = mutations_value->Count(); @@ -4475,7 +4482,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID if (haplosome->scratch_ == 1) { for (slim_position_t position : unique_positions) - species->RecordNewDerivedState(haplosome, position, *haplosome->derived_mutation_ids_at_position(position)); + species->RecordNewDerivedState(haplosome, position, *haplosome->derived_mutation_ids_at_position(mut_block_ptr, position)); haplosome->scratch_ = 0; } } @@ -4533,7 +4540,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID for (int mut_index = value_index; mut_index < mutations_count; ++mut_index) { Mutation *mut_to_remove = mutations_to_remove[mut_index]; - MutationIndex mut_to_remove_index = mut_to_remove->BlockIndex(); + MutationIndex mut_to_remove_index = mutation_block->IndexInBlock(mut_to_remove); if (mut_to_remove_index == candidate_mutation) { @@ -4586,7 +4593,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID std::vector &haplosome_positions = haplosome_pair.second; for (slim_position_t position : haplosome_positions) - species->RecordNewDerivedState(target_haplosome, position, *target_haplosome->derived_mutation_ids_at_position(position)); + species->RecordNewDerivedState(target_haplosome, position, *target_haplosome->derived_mutation_ids_at_position(mut_block_ptr, position)); } } } @@ -4623,6 +4630,11 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID #pragma mark HaplosomeWalker #pragma mark - +HaplosomeWalker::HaplosomeWalker(Haplosome *p_haplosome) : haplosome_(p_haplosome), mutrun_index_(-1), mutrun_ptr_(nullptr), mutrun_end_(nullptr), mutation_(nullptr), mut_block_ptr_(haplosome_->individual_->subpopulation_->species_.SpeciesMutationBlock()->mutation_buffer_) +{ + NextMutation(); +}; + void HaplosomeWalker::NextMutation(void) { // the !mutrun_ptr_ is actually not necessary, but ASAN wants it to be here... @@ -4645,7 +4657,7 @@ void HaplosomeWalker::NextMutation(void) while (mutrun_ptr_ == mutrun_end_); } - mutation_ = gSLiM_Mutation_Block + *mutrun_ptr_; + mutation_ = mut_block_ptr_ + *mutrun_ptr_; } void HaplosomeWalker::MoveToPosition(slim_position_t p_position) @@ -4683,7 +4695,7 @@ void HaplosomeWalker::MoveToPosition(slim_position_t p_position) } // if the mutation found is at or after the requested position, we are already done - mutation_ = gSLiM_Mutation_Block + *mutrun_ptr_; + mutation_ = mut_block_ptr_ + *mutrun_ptr_; if (mutation_->position_ >= p_position) return; @@ -4716,7 +4728,7 @@ bool HaplosomeWalker::MutationIsStackedAtCurrentPosition(Mutation *p_search_mut) for (const MutationIndex *search_ptr_ = mutrun_ptr_; search_ptr_ != mutrun_end_; ++search_ptr_) { MutationIndex mutindex = *search_ptr_; - Mutation *mut = gSLiM_Mutation_Block + mutindex; + Mutation *mut = mut_block_ptr_ + mutindex; if (mut == p_search_mut) return true; @@ -4751,8 +4763,8 @@ bool HaplosomeWalker::IdenticalAtCurrentPositionTo(HaplosomeWalker &p_other_walk do { - Mutation *mut_1 = (search_ptr_1 != mutrun_end_) ? (gSLiM_Mutation_Block + *search_ptr_1) : nullptr; - Mutation *mut_2 = (search_ptr_2 != p_other_walker.mutrun_end_) ? (gSLiM_Mutation_Block + *search_ptr_2) : nullptr; + Mutation *mut_1 = (search_ptr_1 != mutrun_end_) ? (mut_block_ptr_ + *search_ptr_1) : nullptr; + Mutation *mut_2 = (search_ptr_2 != p_other_walker.mutrun_end_) ? (mut_block_ptr_ + *search_ptr_2) : nullptr; bool has_mut_at_position_1 = (mut_1) ? (mut_1->position_ == pos) : false; bool has_mut_at_position_2 = (mut_2) ? (mut_2->position_ == pos) : false; @@ -4792,7 +4804,7 @@ int8_t HaplosomeWalker::NucleotideAtCurrentPosition(void) for (const MutationIndex *search_ptr_ = mutrun_ptr_ + 1; search_ptr_ != mutrun_end_; ++search_ptr_) { MutationIndex mutindex = *search_ptr_; - Mutation *mut = gSLiM_Mutation_Block + mutindex; + Mutation *mut = mut_block_ptr_ + mutindex; if (mut->position_ != pos) return -1; diff --git a/core/haplosome.h b/core/haplosome.h index 30b95464..7df2b7f2 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -62,6 +62,7 @@ class Population; class Subpopulation; class Individual; class HaplosomeWalker; +class MutationBlock; extern EidosClass *gSLiM_Haplosome_Class; @@ -284,7 +285,7 @@ class Haplosome : public EidosObject static void BulkOperationEnd(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index); // Remove all mutations in p_haplosome that have a state_ of MutationState::kFixedAndSubstituted, indicating that they have fixed - void RemoveFixedMutations(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index) + inline __attribute__((always_inline)) void RemoveFixedMutations(Mutation *p_mut_block_ptr, int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index) { #if DEBUG if (mutrun_count_ == 0) @@ -296,7 +297,7 @@ class Haplosome : public EidosObject // Population::RemoveAllFixedMutations() for further context on this. MutationRun *mutrun = const_cast(mutruns_[p_mutrun_index]); - mutrun->RemoveFixedMutations(p_operation_id); + mutrun->RemoveFixedMutations(p_mut_block_ptr, p_operation_id); } // TallyHaplosomeReferences_Checkback() counts up the total MutationRun references, using their usage counts, as a checkback @@ -392,20 +393,20 @@ class Haplosome : public EidosObject // subpop_ = p_source_haplosome.subpop_; } - inline const std::vector *derived_mutation_ids_at_position(slim_position_t p_position) const + inline const std::vector *derived_mutation_ids_at_position(Mutation *p_mut_block_ptr, slim_position_t p_position) const { slim_mutrun_index_t run_index = (slim_mutrun_index_t)(p_position / mutrun_length_); - return mutruns_[run_index]->derived_mutation_ids_at_position(p_position); + return mutruns_[run_index]->derived_mutation_ids_at_position(p_mut_block_ptr, p_position); } void record_derived_states(Species *p_species) const; // print the sample represented by haplosomes, using SLiM's own format - static void PrintHaplosomes_SLiM(std::ostream &p_out, std::vector &p_haplosomes, bool p_output_object_tags); + static void PrintHaplosomes_SLiM(std::ostream &p_out, Species &p_species, std::vector &p_haplosomes, bool p_output_object_tags); // print the sample represented by haplosomes, using "ms" format - static void PrintHaplosomes_MS(std::ostream &p_out, std::vector &p_haplosomes, const Chromosome &p_chromosome, bool p_filter_monomorphic); + static void PrintHaplosomes_MS(std::ostream &p_out, Species &p_species, std::vector &p_haplosomes, const Chromosome &p_chromosome, bool p_filter_monomorphic); // print the sample represented by haplosomes, using "vcf" format static void PrintHaplosomes_VCF(std::ostream &p_out, std::vector &p_haplosomes, const Chromosome &p_chromosome, bool groupAsIndividuals, bool p_output_multiallelics, bool p_simplify_nucs, bool p_output_nonnucs); @@ -493,13 +494,14 @@ class HaplosomeWalker const MutationIndex *mutrun_ptr_; // a pointer to the current element in the mutation run const MutationIndex *mutrun_end_; // an end pointer for the mutation run Mutation *mutation_; // the current mutation pointer, or nullptr if we have reached the end of the haplosome + Mutation *mut_block_ptr_; // a cached mutation block buffer pointer for our haplosome's species public: HaplosomeWalker(void) = delete; HaplosomeWalker(const HaplosomeWalker &p_original) = default; HaplosomeWalker& operator= (const HaplosomeWalker &p_original) = default; - inline HaplosomeWalker(Haplosome *p_haplosome) : haplosome_(p_haplosome), mutrun_index_(-1), mutrun_ptr_(nullptr), mutrun_end_(nullptr), mutation_(nullptr) { NextMutation(); }; + explicit HaplosomeWalker(Haplosome *p_haplosome); HaplosomeWalker(HaplosomeWalker&&) = default; inline ~HaplosomeWalker(void) {}; diff --git a/core/individual.cpp b/core/individual.cpp index 69317e4c..8f8d7017 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -22,6 +22,7 @@ #include "subpopulation.h" #include "species.h" #include "community.h" +#include "mutation_block.h" #include "eidos_property_signature.h" #include "eidos_call_signature.h" #include "polymorphism.h" @@ -870,7 +871,7 @@ void Individual::PrintIndividuals_SLiM(std::ostream &p_out, const Individual **p int first_haplosome_index = species.FirstHaplosomeIndices()[chromosome_index]; int last_haplosome_index = species.LastHaplosomeIndices()[chromosome_index]; PolymorphismMap polymorphisms; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; // add all polymorphisms for this chromosome for (int64_t individual_index = 0; individual_index < p_individuals_count; ++individual_index) @@ -1465,7 +1466,7 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) vec->reserve(total_mutation_count); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; for (Chromosome *chromosome : species.Chromosomes()) { @@ -3038,7 +3039,7 @@ void Individual::SetProperty_Accelerated_age(EidosGlobalStringID p_property_id, void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { -#pragma unused (p_property_id) +#pragma unused (p_property_id, p_source_size) const Individual **individuals_buffer = (const Individual **)p_values; Species *species = Community::SpeciesForIndividualsVector(individuals_buffer, (int)p_values_size); const double *source_data = p_source.FloatData(); @@ -3184,7 +3185,7 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_countOfMutationsOfType(Eidos MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species->community_, species, "countOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK // Count the number of mutations of the given type - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species->SpeciesMutationBlock()->mutation_buffer_; EidosValue_Int *integer_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_elements_size); int haplosome_count_per_individual = species->HaplosomeCountPerIndividual(); @@ -3421,7 +3422,7 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species->community_, species, "sumOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK // Sum the selection coefficients of mutations of the given type - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species->SpeciesMutationBlock()->mutation_buffer_; EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_elements_size); int haplosome_count_per_individual = species->HaplosomeCountPerIndividual(); @@ -3530,7 +3531,7 @@ EidosValue_SP Individual::ExecuteMethod_uniqueMutationsOfType(EidosGlobalStringI if (only_haploid_haplosomes || (vec_reserve_size < 100)) // an arbitrary limit, but we don't want to make something *too* unnecessarily big... vec->reserve(vec_reserve_size); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; for (Chromosome *chromosome : species.Chromosomes()) { @@ -3802,7 +3803,7 @@ EidosValue_SP Individual::ExecuteMethod_mutationsFromHaplosomes(EidosGlobalStrin // loop through the chromosomes EidosValue_Object *vec = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Mutation_Class)); EidosValue_SP result_SP = EidosValue_SP(vec); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; for (slim_chromosome_index_t chromosome_index : chromosome_indices) { @@ -4501,7 +4502,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_outputIndividualsToVCF(EidosGlobal inline __attribute__((always_inline)) static void _AddCallToHaplosome(int call, Haplosome *haplosome, slim_mutrun_index_t &haplosome_last_mutrun_modified, MutationRun *&haplosome_last_mutrun, std::vector &alt_allele_mut_indices, slim_position_t mut_position, Species *species, MutationRunContext *mutrun_context, - bool all_target_haplosomes_started_empty, bool recording_mutations) + Mutation *mut_block_ptr, bool all_target_haplosomes_started_empty, bool recording_mutations) { if (call == 0) return; @@ -4527,10 +4528,10 @@ _AddCallToHaplosome(int call, Haplosome *haplosome, slim_mutrun_index_t &haploso if (all_target_haplosomes_started_empty) haplosome_last_mutrun->emplace_back(mut_index); else - haplosome_last_mutrun->insert_sorted_mutation(mut_index); + haplosome_last_mutrun->insert_sorted_mutation(mut_block_ptr, mut_index); if (recording_mutations) - species->RecordNewDerivedState(haplosome, mut_position, *haplosome->derived_mutation_ids_at_position(mut_position)); + species->RecordNewDerivedState(haplosome, mut_position, *haplosome->derived_mutation_ids_at_position(mut_block_ptr, mut_position)); } // ********************* + (o)readIndividualsFromVCF(s$ filePath = NULL, [Nio mutationType = NULL]) @@ -4553,6 +4554,8 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (!species) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): " << "readIndividualsFromVCF() requires that all target individuals belong to the same species." << EidosTerminate(); + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; Individual * const *individuals_data = (Individual * const *)p_target->ObjectData(); int individuals_size = p_target->Count(); @@ -5032,7 +5035,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal } // instantiate the mutation with the values decided upon - MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); + MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); Mutation *new_mut; if (info_mutids.size() > 0) @@ -5040,12 +5043,12 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal // a mutation ID was supplied; we use it blindly, having checked above that we are in the case where this is legal slim_mutationid_t mut_mutid = info_mutids[alt_allele_index]; - new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mut_mutid, mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); + new_mut = new (mut_block_ptr + new_mut_index) Mutation(mut_mutid, mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } else { // no mutation ID supplied, so use whatever is next - new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); + new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ @@ -5265,14 +5268,14 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal // add the called mutation to the haplosome at haplosomes_index _AddCallToHaplosome(genotype_call1, haplosomes[haplosomes_index], haplosomes_last_mutrun_modified[haplosomes_index], haplosomes_last_mutrun[haplosomes_index], - alt_allele_mut_indices, mut_position, species, &mutrun_context, + alt_allele_mut_indices, mut_position, species, &mutrun_context, mut_block_ptr, all_target_haplosomes_started_empty, recording_mutations); } else if (haplosomes[haplosomes_index + 1]) { // add the called mutation to the haplosome at haplosomes_index + 1 _AddCallToHaplosome(genotype_call1, haplosomes[haplosomes_index + 1], haplosomes_last_mutrun_modified[haplosomes_index + 1], haplosomes_last_mutrun[haplosomes_index + 1], - alt_allele_mut_indices, mut_position, species, &mutrun_context, + alt_allele_mut_indices, mut_position, species, &mutrun_context, mut_block_ptr, all_target_haplosomes_started_empty, recording_mutations); } else @@ -5286,7 +5289,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal { // add the called mutation to the haplosome at haplosomes_index _AddCallToHaplosome(genotype_call1, haplosomes[haplosomes_index], haplosomes_last_mutrun_modified[haplosomes_index], haplosomes_last_mutrun[haplosomes_index], - alt_allele_mut_indices, mut_position, species, &mutrun_context, + alt_allele_mut_indices, mut_position, species, &mutrun_context, mut_block_ptr, all_target_haplosomes_started_empty, recording_mutations); } else @@ -5306,7 +5309,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (haplosomes[haplosomes_index]) { _AddCallToHaplosome(genotype_call1, haplosomes[haplosomes_index], haplosomes_last_mutrun_modified[haplosomes_index], haplosomes_last_mutrun[haplosomes_index], - alt_allele_mut_indices, mut_position, species, &mutrun_context, + alt_allele_mut_indices, mut_position, species, &mutrun_context, mut_block_ptr, all_target_haplosomes_started_empty, recording_mutations); } else @@ -5318,7 +5321,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (haplosomes[haplosomes_index]) { _AddCallToHaplosome(genotype_call2, haplosomes[haplosomes_index], haplosomes_last_mutrun_modified[haplosomes_index], haplosomes_last_mutrun[haplosomes_index], - alt_allele_mut_indices, mut_position, species, &mutrun_context, + alt_allele_mut_indices, mut_position, species, &mutrun_context, mut_block_ptr, all_target_haplosomes_started_empty, recording_mutations); } else @@ -5335,7 +5338,6 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal } // Return the instantiated mutations - Mutation *mut_block_ptr = gSLiM_Mutation_Block; int mutation_count = (int)mutation_indices.size(); EidosValue_Object *vec = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Mutation_Class))->resize_no_initialize_RR(mutation_count); diff --git a/core/mutation.cpp b/core/mutation.cpp index 4a6e9edf..637fadb3 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -22,6 +22,7 @@ #include "eidos_call_signature.h" #include "eidos_property_signature.h" #include "species.h" +#include "mutation_block.h" #include #include @@ -30,198 +31,6 @@ #include -// All Mutation objects get allocated out of a single shared block, for speed; see SLiM_WarmUp() -// Note this is shared by all species; the mutations for every species come out of the same shared block. -Mutation *gSLiM_Mutation_Block = nullptr; -MutationIndex gSLiM_Mutation_Block_Capacity = 0; -MutationIndex gSLiM_Mutation_FreeIndex = -1; -MutationIndex gSLiM_Mutation_Block_LastUsedIndex = -1; - -#ifdef DEBUG_LOCKS_ENABLED -EidosDebugLock gSLiM_Mutation_LOCK("gSLiM_Mutation_LOCK"); -#endif - -slim_refcount_t *gSLiM_Mutation_Refcounts = nullptr; - -#define SLIM_MUTATION_BLOCK_INITIAL_SIZE 16384 // makes for about a 1 MB block; not unreasonable // NOLINT(*-macro-to-enum) : this is fine - -extern std::vector gEidosValue_Object_Mutation_Registry; // this is in Eidos; see SLiM_IncreaseMutationBlockCapacity() - -void SLiM_CreateMutationBlock(void) -{ - THREAD_SAFETY_IN_ANY_PARALLEL("SLiM_CreateMutationBlock(): gSLiM_Mutation_Block address change"); - - // first allocate the block; no need to zero the memory - gSLiM_Mutation_Block_Capacity = SLIM_MUTATION_BLOCK_INITIAL_SIZE; - gSLiM_Mutation_Block = (Mutation *)malloc(gSLiM_Mutation_Block_Capacity * sizeof(Mutation)); - gSLiM_Mutation_Refcounts = (slim_refcount_t *)malloc(gSLiM_Mutation_Block_Capacity * sizeof(slim_refcount_t)); - - if (!gSLiM_Mutation_Block || !gSLiM_Mutation_Refcounts) - EIDOS_TERMINATION << "ERROR (SLiM_CreateMutationBlock): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - - //std::cout << "Allocating initial mutation block, " << SLIM_MUTATION_BLOCK_INITIAL_SIZE * sizeof(Mutation) << " bytes (sizeof(Mutation) == " << sizeof(Mutation) << ")" << std::endl; - - // now we need to set up our free list inside the block; initially all blocks are free - for (MutationIndex i = 0; i < gSLiM_Mutation_Block_Capacity - 1; ++i) - *(MutationIndex *)(gSLiM_Mutation_Block + i) = i + 1; - - *(MutationIndex *)(gSLiM_Mutation_Block + gSLiM_Mutation_Block_Capacity - 1) = -1; - - // now that the block is set up, we can start the free list - gSLiM_Mutation_FreeIndex = 0; -} - -void SLiM_IncreaseMutationBlockCapacity(void) -{ - // We do not use a THREAD_SAFETY macro here because this needs to be checked in release builds also; - // we are not able to completely protect against this occurring at runtime, and it corrupts the run. - // It's OK for this to be called when we're inside an inactive parallel region; there is then no - // race condition. When a parallel region is active, even inside a critical region, reallocating - // the mutation block has the potential for a race with other threads. - if (omp_in_parallel()) - { - std::cerr << "ERROR (SLiM_IncreaseMutationBlockCapacity): (internal error) SLiM_IncreaseMutationBlockCapacity() was called to reallocate gSLiM_Mutation_Block inside a parallel section. If you see this message, you need to increase the pre-allocation margin for your simulation, because it is generating such an unexpectedly large number of new mutations. Please contact the SLiM developers for guidance on how to do this." << std::endl; - raise(SIGTRAP); - } - -#ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.start_critical(1); -#endif - - if (!gSLiM_Mutation_Block) - EIDOS_TERMINATION << "ERROR (SLiM_IncreaseMutationBlockCapacity): (internal error) called before SLiM_CreateMutationBlock()." << EidosTerminate(); - - // We need to expand the size of our Mutation block. This has the consequence of invalidating - // every Mutation * in the program. In general that is fine; we are careful to only keep - // pointers to Mutation temporarily, and for long-term reference we use MutationIndex. The - // exception to this is EidosValue_Object; the user can put references to mutations into - // variables that need to remain valid across reallocs like this. We therefore have to hunt - // down every EidosValue_Object that contains Mutations, and fix the pointer inside each of - // them. Because in SLiMgui all of the running simulations share a single Mutation block at - // the moment, in SLiMgui this patching has to occur across all of the simulations, not just - // the one that made this call. Yes, this is very gross. This is why pointers are evil. :-> - - // First let's do our realloc. We just need to note the change in value for the pointer. - // For now we will just double in size; we don't want to waste too much memory, but we - // don't want to have to realloc too often, either. - // BCH 11 May 2020: the realloc of gSLiM_Mutation_Block is technically problematic, because - // Mutation is non-trivially copyable according to C++. But it is safe, so I cast to void* - // in the hopes that that will make the warning go away. - std::uintptr_t old_mutation_block = reinterpret_cast(gSLiM_Mutation_Block); - MutationIndex old_block_capacity = gSLiM_Mutation_Block_Capacity; - - //std::cout << "old capacity: " << old_block_capacity << std::endl; - - // BCH 25 July 2023: check for increasing our block beyond the maximum size of 2^31 mutations. - // See https://github.com/MesserLab/SLiM/issues/361. Note that the initial size should be - // a power of 2, so that we actually reach the maximum; see SLIM_MUTATION_BLOCK_INITIAL_SIZE. - // In other words, we expect to be at exactly 0x0000000040000000UL here, and thus to double - // to 0x0000000080000000UL, which is a capacity of 2^31, which is the limit of int32_t. - if ((size_t)old_block_capacity > 0x0000000040000000UL) // >2^30 means >2^31 when doubled - EIDOS_TERMINATION << "ERROR (SLiM_IncreaseMutationBlockCapacity): too many mutations; there is a limit of 2^31 (2147483648) segregating mutations in SLiM." << EidosTerminate(nullptr); - - gSLiM_Mutation_Block_Capacity *= 2; - gSLiM_Mutation_Block = (Mutation *)realloc((void*)gSLiM_Mutation_Block, gSLiM_Mutation_Block_Capacity * sizeof(Mutation)); // NOLINT(*-realloc-usage) : realloc failure is a fatal error anyway - gSLiM_Mutation_Refcounts = (slim_refcount_t *)realloc(gSLiM_Mutation_Refcounts, gSLiM_Mutation_Block_Capacity * sizeof(slim_refcount_t)); // NOLINT(*-realloc-usage) : realloc failure is a fatal error anyway - - if (!gSLiM_Mutation_Block || !gSLiM_Mutation_Refcounts) - EIDOS_TERMINATION << "ERROR (SLiM_IncreaseMutationBlockCapacity): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - - //std::cout << "new capacity: " << gSLiM_Mutation_Block_Capacity << std::endl; - - std::uintptr_t new_mutation_block = reinterpret_cast(gSLiM_Mutation_Block); - - // Set up the free list to extend into the new portion of the buffer. If we are called when - // gSLiM_Mutation_FreeIndex != -1, the free list will start with the new region. - for (MutationIndex i = old_block_capacity; i < gSLiM_Mutation_Block_Capacity - 1; ++i) - *(MutationIndex *)(gSLiM_Mutation_Block + i) = i + 1; - - *(MutationIndex *)(gSLiM_Mutation_Block + gSLiM_Mutation_Block_Capacity - 1) = gSLiM_Mutation_FreeIndex; - - gSLiM_Mutation_FreeIndex = old_block_capacity; - - // Now we go out and fix Mutation * references in EidosValue_Object in all symbol tables - if (new_mutation_block != old_mutation_block) - { - // This may be excessively cautious, but I want to avoid subtracting these uintptr_t values - // to produce a negative number; that seems unwise and possibly platform-dependent. - if (old_mutation_block > new_mutation_block) - { - std::uintptr_t ptr_diff = old_mutation_block - new_mutation_block; - - for (EidosValue_Object *mutation_value : gEidosValue_Object_Mutation_Registry) - mutation_value->PatchPointersBySubtracting(ptr_diff); - } - else - { - std::uintptr_t ptr_diff = new_mutation_block - old_mutation_block; - - for (EidosValue_Object *mutation_value : gEidosValue_Object_Mutation_Registry) - mutation_value->PatchPointersByAdding(ptr_diff); - } - } - -#ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.end_critical(); -#endif -} - -void SLiM_ZeroRefcountBlock(MutationRun &p_mutation_registry, bool p_registry_only) -{ - THREAD_SAFETY_IN_ANY_PARALLEL("SLiM_ZeroRefcountBlock(): gSLiM_Mutation_Block change"); - -#ifdef SLIMGUI - // BCH 11/25/2017: This code path needs to be used in SLiMgui to avoid modifying the refcounts - // for mutations in other simulations sharing the mutation block. - p_registry_only = true; -#endif - - if (p_registry_only) - { - // This code path zeros out refcounts just for the mutations currently in use in the registry. - // It is thus minimal, but probably quite a bit slower than just zeroing out the whole thing. - // BCH 6/8/2023: This is necessary in SLiMgui, as noted above, but also in multispecies sims - // so that one species does not step on the toes of another species. - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; - const MutationIndex *registry_iter = p_mutation_registry.begin_pointer_const(); - const MutationIndex *registry_iter_end = p_mutation_registry.end_pointer_const(); - - while (registry_iter != registry_iter_end) - *(refcount_block_ptr + (*registry_iter++)) = 0; - } - else - { - // Zero out the whole thing with EIDOS_BZERO(), without worrying about which bits are in use. - // This hits more memory, but avoids having to read the registry, and should write whole cache lines. - EIDOS_BZERO(gSLiM_Mutation_Refcounts, (gSLiM_Mutation_Block_LastUsedIndex + 1) * sizeof(slim_refcount_t)); - } -} - -size_t SLiMMemoryUsageForMutationBlock(void) -{ - return gSLiM_Mutation_Block_Capacity * sizeof(Mutation); -} - -size_t SLiMMemoryUsageForFreeMutations(void) -{ - size_t mut_count = 0; - MutationIndex nextFreeBlock = gSLiM_Mutation_FreeIndex; - - while (nextFreeBlock != -1) - { - mut_count++; - nextFreeBlock = *(MutationIndex *)(gSLiM_Mutation_Block + nextFreeBlock); - } - - return mut_count * sizeof(Mutation); -} - -size_t SLiMMemoryUsageForMutationRefcounts(void) -{ - return gSLiM_Mutation_Block_Capacity * sizeof(slim_refcount_t); -} - - #pragma mark - #pragma mark Mutation #pragma mark - @@ -233,9 +42,12 @@ Mutation::Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(p_selection_coeff), dominance_coeff_(p_dominance_coeff), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(gSLiM_next_mutation_id++) { #ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.start_critical(2); + mutation_block_LOCK.start_critical(2); #endif + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + // initialize the tag to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; @@ -244,15 +56,43 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); - // zero out our refcount, which is now kept in a separate buffer - gSLiM_Mutation_Refcounts[BlockIndex()] = 0; + // zero out our refcount and per-trait information, which is now kept in a separate buffer + // FIXME MULTITRAIT: The per-trait info will soon supplant selection_coeff_ and dominance_coeff_; this initialization code needs to be fleshed out + MutationIndex mutation_index = mutation_block->IndexInBlock(this); + mutation_block->refcount_buffer_[mutation_index] = 0; + + int trait_count = mutation_block->trait_count_; + MutationTraitInfo *traitInfoBase = mutation_block->trait_info_buffer_ + trait_count * mutation_index; + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + MutationTraitInfo *traitInfoRec = traitInfoBase + trait_index; + Trait *trait = species.Traits()[trait_index]; + TraitType traitType = trait->Type(); + + traitInfoRec->mutation_effect_ = selection_coeff_; + traitInfoRec->dominance_coeff_ = dominance_coeff_; + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = 0.0; + traitInfoRec->heterozygous_effect_ = 0.0; + traitInfoRec->hemizygous_effect_ = 0.0; + } + else + { + traitInfoRec->homozygous_effect_ = 0.0; + traitInfoRec->heterozygous_effect_ = 0.0; + traitInfoRec->hemizygous_effect_ = 0.0; + } + } #if DEBUG_MUTATIONS std::cout << "Mutation constructed: " << this << std::endl; #endif #ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.end_critical(); + mutation_block_LOCK.end_critical(); #endif #if 0 @@ -303,6 +143,9 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ Mutation::Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(p_selection_coeff), dominance_coeff_(p_dominance_coeff), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id) { + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + // initialize the tag to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; @@ -311,8 +154,36 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); - // zero out our refcount, which is now kept in a separate buffer - gSLiM_Mutation_Refcounts[BlockIndex()] = 0; + // zero out our refcount and per-trait information, which is now kept in a separate buffer + // FIXME MULTITRAIT: The per-trait info will soon supplant selection_coeff_ and dominance_coeff_; this initialization code needs to be fleshed out + MutationIndex mutation_index = mutation_block->IndexInBlock(this); + mutation_block->refcount_buffer_[mutation_index] = 0; + + int trait_count = mutation_block->trait_count_; + MutationTraitInfo *traitInfoBase = mutation_block->trait_info_buffer_ + trait_count * mutation_index; + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + MutationTraitInfo *traitInfoRec = traitInfoBase + trait_index; + Trait *trait = species.Traits()[trait_index]; + TraitType traitType = trait->Type(); + + traitInfoRec->mutation_effect_ = selection_coeff_; + traitInfoRec->dominance_coeff_ = dominance_coeff_; + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = 0.0; + traitInfoRec->heterozygous_effect_ = 0.0; + traitInfoRec->hemizygous_effect_ = 0.0; + } + else + { + traitInfoRec->homozygous_effect_ = 0.0; + traitInfoRec->heterozygous_effect_ = 0.0; + traitInfoRec->hemizygous_effect_ = 0.0; + } + } #if DEBUG_MUTATIONS std::cout << "Mutation constructed: " << this << std::endl; @@ -331,10 +202,12 @@ void Mutation::SelfDelete(void) { // This is called when our retain count reaches zero // We destruct ourselves and return our memory to our shared pool - MutationIndex mutation_index = BlockIndex(); + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationIndex mutation_index = mutation_block->IndexInBlock(this); this->~Mutation(); - SLiM_DisposeMutationToBlock(mutation_index); + mutation_block->DisposeMutationToBlock(mutation_index); } // This is unused except by debugging code and in the debugger itself diff --git a/core/mutation.h b/core/mutation.h index b8ecfeb7..881ab7a1 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -41,7 +41,7 @@ extern EidosClass *gSLiM_Mutation_Class; // A global counter used to assign all Mutation objects a unique ID extern slim_mutationid_t gSLiM_next_mutation_id; -// A MutationIndex is an index into gSLiM_Mutation_Block (see below); it is used as, in effect, a Mutation *, but is 32-bit. +// A MutationIndex is an index into a MutationBlock (see mutation_block.h); it is used as, in effect, a Mutation *, but is 32-bit. // Note that type int32_t is used instead of uint32_t so that -1 can be used as a "null pointer"; perhaps UINT32_MAX would be // better, but on the other hand using int32_t has the virtue that if we run out of room we will probably crash hard rather // than perhaps just silently overrunning gSLiM_Mutation_Block with mysterious memory corruption bugs that are hard to catch. @@ -51,11 +51,25 @@ extern slim_mutationid_t gSLiM_next_mutation_id; // difficult to code since MutationRun's internal buffer of MutationIndex is accessible and used directly by many clients. typedef int32_t MutationIndex; -// forward declaration of Mutation block allocation; see bottom of header -class Mutation; -extern Mutation *gSLiM_Mutation_Block; -extern MutationIndex gSLiM_Mutation_Block_Capacity; - +// This structure contains all of the information about how a mutation influences a particular trait: in particular, its +// effect size and dominance coefficient. Each mutation keeps this information for each trait in its species, and since +// the number of traits is determined at runtime, the size of this data -- the number of MutationTraitInfo records kept +// by each mutation -- is also determined at runtime. We don't want to make a separate malloced block for each mutation; +// that would be far too expensive. Instead, MutationBlock keeps a block of MutationTraitInfo records for the species, +// with a number of records per mutation that is determined when it is constructed. +typedef struct _MutationTraitInfo +{ + slim_effect_t mutation_effect_; // selection coefficient (s) or additive effect (a) + slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + + // We cache values used in the fitness calculation code, for speed. These are the final fitness effects of this mutation + // when it is homozygous or heterozygous, respectively. These values are clamped to a minimum of 0.0, so that multiplying + // by them cannot cause the fitness of the individual to go below 0.0, avoiding slow tests in the core fitness loop. These + // values use slim_effect_t for speed; roundoff should not be a concern, since such differences would be inconsequential. + slim_effect_t homozygous_effect_; // a cached value for (1 + selection_coeff_), clamped to 0.0 minimum + slim_effect_t heterozygous_effect_; // a cached value for (1 + dominance_coeff * selection_coeff_), clamped to 0.0 minimum + slim_effect_t hemizygous_effect_; // a cached value for (1 + hemizygous_dominance_coeff_ * selection_coeff_), clamped to 0.0 minimum +} MutationTraitInfo; typedef enum { kNewMutation = 0, // the state after new Mutation() @@ -122,8 +136,6 @@ class Mutation : public EidosDictionaryRetained virtual void SelfDelete(void) override; - inline __attribute__((always_inline)) MutationIndex BlockIndex(void) const { return (MutationIndex)(this - gSLiM_Mutation_Block); } - // // Eidos support // @@ -186,73 +198,6 @@ class Mutation_Class : public EidosDictionaryRetained_Class virtual const std::vector *Methods(void) const override; }; - -// -// Mutation block allocation -// - -// All Mutation objects get allocated out of a single shared pool, for speed. We do not use EidosObjectPool for this -// any more, because we need the allocation to be out of a single contiguous block of memory that we realloc as needed, -// allowing Mutation objects to be referred to using 32-bit indexes into this contiguous block. So we have a custom -// pool, declared here and implemented in mutation.cpp. Note that this is a global, to make it easy for users of -// MutationIndex to look up mutations without needing to track down a pointer to the mutation block from the sim. This -// means that in SLiMgui a single block will be used for all mutations in all simulations; that should be harmless. -class MutationRun; - -extern Mutation *gSLiM_Mutation_Block; -extern MutationIndex gSLiM_Mutation_FreeIndex; -extern MutationIndex gSLiM_Mutation_Block_LastUsedIndex; - -#ifdef DEBUG_LOCKS_ENABLED -// We do not arbitrate access to the mutation block with a lock; instead, we expect that clients -// will manage their own multithreading issues. In DEBUG mode we check for incorrect uses (races). -// We use this lock to check. Any failure to acquire the lock indicates a race. -extern EidosDebugLock gSLiM_Mutation_LOCK; -#endif - -extern slim_refcount_t *gSLiM_Mutation_Refcounts; // an auxiliary buffer, parallel to gSLiM_Mutation_Block, to increase memory cache efficiency - // note that I tried keeping the fitness cache values and positions in separate buffers too, not a win -void SLiM_CreateMutationBlock(void); -void SLiM_IncreaseMutationBlockCapacity(void); -void SLiM_ZeroRefcountBlock(MutationRun &p_mutation_registry, bool p_registry_only); -size_t SLiMMemoryUsageForMutationBlock(void); -size_t SLiMMemoryUsageForFreeMutations(void); -size_t SLiMMemoryUsageForMutationRefcounts(void); - -inline __attribute__((always_inline)) MutationIndex SLiM_NewMutationFromBlock(void) -{ -#ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.start_critical(0); -#endif - - if (gSLiM_Mutation_FreeIndex == -1) - SLiM_IncreaseMutationBlockCapacity(); - - MutationIndex result = gSLiM_Mutation_FreeIndex; - - gSLiM_Mutation_FreeIndex = *(MutationIndex *)(gSLiM_Mutation_Block + result); - - if (gSLiM_Mutation_Block_LastUsedIndex < result) - gSLiM_Mutation_Block_LastUsedIndex = result; - -#ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.end_critical(); -#endif - - return result; // no need to zero out the memory, we are just an allocater, not a constructor -} - -inline __attribute__((always_inline)) void SLiM_DisposeMutationToBlock(MutationIndex p_mutation_index) -{ - THREAD_SAFETY_IN_ACTIVE_PARALLEL("SLiM_DisposeMutationToBlock(): gSLiM_Mutation_Block change"); - - void *mut_ptr = gSLiM_Mutation_Block + p_mutation_index; - - *(MutationIndex *)mut_ptr = gSLiM_Mutation_FreeIndex; - gSLiM_Mutation_FreeIndex = p_mutation_index; -} - - #endif /* defined(__SLiM__mutation__) */ diff --git a/core/mutation_block.cpp b/core/mutation_block.cpp new file mode 100644 index 00000000..cc238699 --- /dev/null +++ b/core/mutation_block.cpp @@ -0,0 +1,292 @@ +// +// mutation_block.cpp +// SLiM +// +// Created by Ben Haller on 10/14/25. +// Copyright (c) 2014-2025 Benjamin C. Haller. All rights reserved. +// A product of the Messer Lab, http://messerlab.org/slim/ +// + +// This file is part of SLiM. +// +// SLiM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// SLiM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with SLiM. If not, see . + + +#include "mutation_block.h" +#include "mutation_run.h" + + +#define SLIM_MUTATION_BLOCK_INITIAL_SIZE 16384 // makes for about a 1 MB block; not unreasonable // NOLINT(*-macro-to-enum) : this is fine + + +MutationBlock::MutationBlock(Species &p_species, int p_trait_count) : species_(p_species), trait_count_(p_trait_count) +{ + THREAD_SAFETY_IN_ANY_PARALLEL("SLiM_CreateMutationBlock(): mutation_buffer_ address change"); + + // first allocate our buffers; no need to zero the memory + capacity_ = SLIM_MUTATION_BLOCK_INITIAL_SIZE; + mutation_buffer_ = (Mutation *)malloc(capacity_ * sizeof(Mutation)); + refcount_buffer_ = (slim_refcount_t *)malloc(capacity_ * sizeof(slim_refcount_t)); + trait_info_buffer_ = (MutationTraitInfo *)malloc(capacity_ * (sizeof(MutationTraitInfo) * trait_count_)); + + if (!mutation_buffer_ || !refcount_buffer_ || !trait_info_buffer_) + EIDOS_TERMINATION << "ERROR (SLiM_CreateMutationBlock): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + + //std::cout << "Allocating initial mutation block, " << SLIM_MUTATION_BLOCK_INITIAL_SIZE * sizeof(Mutation) << " bytes (sizeof(Mutation) == " << sizeof(Mutation) << ")" << std::endl; + + // now we need to set up our free list inside the block; initially all blocks are free + for (MutationIndex i = 0; i < capacity_ - 1; ++i) + *(MutationIndex *)(mutation_buffer_ + i) = i + 1; + + *(MutationIndex *)(mutation_buffer_ + capacity_ - 1) = -1; + + // now that the block is set up, we can start the free list + free_index_ = 0; +} + +void MutationBlock::IncreaseMutationBlockCapacity(void) +{ + // We do not use a THREAD_SAFETY macro here because this needs to be checked in release builds also; + // we are not able to completely protect against this occurring at runtime, and it corrupts the run. + // It's OK for this to be called when we're inside an inactive parallel region; there is then no + // race condition. When a parallel region is active, even inside a critical region, reallocating + // the mutation block has the potential for a race with other threads. + if (omp_in_parallel()) + { + std::cerr << "ERROR (MutationBlock::IncreaseMutationBlockCapacity): (internal error) IncreaseMutationBlockCapacity() was called to reallocate mutation_buffer_ inside a parallel section. If you see this message, you need to increase the pre-allocation margin for your simulation, because it is generating such an unexpectedly large number of new mutations. Please contact the SLiM developers for guidance on how to do this." << std::endl; + raise(SIGTRAP); + } + +#ifdef DEBUG_LOCKS_ENABLED + mutation_block_LOCK.start_critical(1); +#endif + + if (!mutation_buffer_) + EIDOS_TERMINATION << "ERROR (MutationBlock::IncreaseMutationBlockCapacity): (internal error) mutation buffer not allocated!" << EidosTerminate(); + + // We need to expand the size of our Mutation block. This has the consequence of invalidating + // every Mutation * in the program. In general that is fine; we are careful to only keep + // pointers to Mutation temporarily, and for long-term reference we use MutationIndex. The + // exception to this is EidosValue_Object; the user can put references to mutations into + // variables that need to remain valid across reallocs like this. We therefore have to hunt + // down every EidosValue_Object that contains Mutations, and fix the pointer inside each of + // them. Yes, this is very gross. This is why pointers are evil. :-> + + // First we need to get a vector containing the memory location of every pointer-to-Mutation* + // in every EidosValue_Object in the whole runtime. This is provided to us by EidosValue_Object, + // which keeps that registry for us. We cache the locations of the pointers to mutations that + // belong to our species. + std::vector &mutation_object_registry = EidosValue_Object::static_EidosValue_Object_Mutation_Registry; + std::vector locations_to_patch; + + for (EidosValue_Object *mutation_value : mutation_object_registry) + { + EidosObject * const *object_buffer = mutation_value->data(); + Mutation * const *mutation_buffer = (Mutation * const *)object_buffer; + int mutation_count = mutation_value->Count(); + + for (int index = 0; index < mutation_count; ++index) + { + Mutation *mutation = mutation_buffer[index]; + MutationType *muttype = mutation->mutation_type_ptr_; + Species *species = &muttype->species_; + + if (species == &species_) + { + // This mutation belongs to our species; so we're about to move it in memory. We need to + // keep a pointer to the memory location where this EidosValue_Object is keeping a pointer + // to it, so that we can patch this pointer after the realloc. + locations_to_patch.push_back(reinterpret_cast(mutation_buffer + index)); + } + } + } + + // Next we do our realloc. We just need to note the change in value for the pointer. + // For now we will just double in size; we don't want to waste too much memory, but we + // don't want to have to realloc too often, either. + // BCH 11 May 2020: the realloc of mutation_buffer_ is technically problematic, because + // Mutation is non-trivially copyable according to C++. But it is safe, so I cast to + // std::uintptr_t to make the warning go away. + std::uintptr_t old_mutation_block = reinterpret_cast(mutation_buffer_); + MutationIndex old_block_capacity = capacity_; + + //std::cout << "old capacity: " << old_block_capacity << std::endl; + + // BCH 25 July 2023: check for increasing our block beyond the maximum size of 2^31 mutations. + // See https://github.com/MesserLab/SLiM/issues/361. Note that the initial size should be + // a power of 2, so that we actually reach the maximum; see SLIM_MUTATION_BLOCK_INITIAL_SIZE. + // In other words, we expect to be at exactly 0x0000000040000000UL here, and thus to double + // to 0x0000000080000000UL, which is a capacity of 2^31, which is the limit of int32_t. + if ((size_t)old_block_capacity > 0x0000000040000000UL) // >2^30 means >2^31 when doubled + EIDOS_TERMINATION << "ERROR (MutationBlock::IncreaseMutationBlockCapacity): too many mutations; there is a limit of 2^31 (2147483648) segregating mutations in SLiM." << EidosTerminate(nullptr); + + capacity_ *= 2; + mutation_buffer_ = (Mutation *)realloc((void*)mutation_buffer_, capacity_ * sizeof(Mutation)); // NOLINT(*-realloc-usage) : realloc failure is a fatal error anyway + refcount_buffer_ = (slim_refcount_t *)realloc(refcount_buffer_, capacity_ * sizeof(slim_refcount_t)); // NOLINT(*-realloc-usage) : realloc failure is a fatal error anyway + trait_info_buffer_ = (MutationTraitInfo *)realloc(trait_info_buffer_, capacity_ * (sizeof(MutationTraitInfo) * trait_count_)); // NOLINT(*-realloc-usage) : realloc failure is a fatal error anyway + + if (!mutation_buffer_ || !refcount_buffer_ || !trait_info_buffer_) + EIDOS_TERMINATION << "ERROR (MutationBlock::IncreaseMutationBlockCapacity): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + + //std::cout << "new capacity: " << capacity_ << std::endl; + + std::uintptr_t new_mutation_block = reinterpret_cast(mutation_buffer_); + + // Set up the free list to extend into the new portion of the buffer. If we are called when + // free_index_ != -1, the free list will start with the new region. + for (MutationIndex i = old_block_capacity; i < capacity_ - 1; ++i) + *(MutationIndex *)(mutation_buffer_ + i) = i + 1; + + *(MutationIndex *)(mutation_buffer_ + capacity_ - 1) = free_index_; + + free_index_ = old_block_capacity; + + // Now we go out and fix Mutation * references in EidosValue_Object in all symbol tables + if (new_mutation_block != old_mutation_block) + { + // This may be excessively cautious, but I want to avoid subtracting these uintptr_t values + // to produce a negative number; that seems unwise and possibly platform-dependent. + if (old_mutation_block > new_mutation_block) + { + std::uintptr_t ptr_diff = old_mutation_block - new_mutation_block; + + for (std::uintptr_t location_to_patch : locations_to_patch) + { + std::uintptr_t *pointer_to_location = reinterpret_cast(location_to_patch); + std::uintptr_t old_element_ptr = *pointer_to_location; + std::uintptr_t new_element_ptr = old_element_ptr - ptr_diff; + + *pointer_to_location = new_element_ptr; + } + } + else + { + std::uintptr_t ptr_diff = new_mutation_block - old_mutation_block; + + for (std::uintptr_t location_to_patch : locations_to_patch) + { + std::uintptr_t *pointer_to_location = reinterpret_cast(location_to_patch); + std::uintptr_t old_element_ptr = *pointer_to_location; + std::uintptr_t new_element_ptr = old_element_ptr + ptr_diff; + + *pointer_to_location = new_element_ptr; + } + } + } + +#ifdef DEBUG_LOCKS_ENABLED + mutation_block_LOCK.end_critical(); +#endif +} + +void MutationBlock::ZeroRefcountBlock(MutationRun &p_mutation_registry) +{ +#pragma unused (p_mutation_registry) + + THREAD_SAFETY_IN_ANY_PARALLEL("SLiM_ZeroRefcountBlock(): mutation_buffer_ change"); + +#if 0 +#ifdef SLIMGUI + // BCH 11/25/2017: This code path needs to be used in SLiMgui to avoid modifying the refcounts + // for mutations in other simulations sharing the mutation block. + p_registry_only = true; +#endif + + if (p_registry_only) + { + // This code path zeros out refcounts just for the mutations currently in use in the registry. + // It is thus minimal, but probably quite a bit slower than just zeroing out the whole thing. + // BCH 6/8/2023: This is necessary in SLiMgui, as noted above, but also in multispecies sims + // so that one species does not step on the toes of another species. + // BCH 10/15/2025: This is no longer needed in any case, since we now keep a separate MutationBlock + // object for each species in each simulation. Keeping the code as a record of this policy shift. + slim_refcount_t *refcount_block_ptr = refcount_buffer_; + const MutationIndex *registry_iter = p_mutation_registry.begin_pointer_const(); + const MutationIndex *registry_iter_end = p_mutation_registry.end_pointer_const(); + + while (registry_iter != registry_iter_end) + *(refcount_block_ptr + (*registry_iter++)) = 0; + + return; + } +#endif + + // Zero out the whole thing with EIDOS_BZERO(), without worrying about which bits are in use. + // This hits more memory, but avoids having to read the registry, and should write whole cache lines. + EIDOS_BZERO(refcount_buffer_, (last_used_index_ + 1) * sizeof(slim_refcount_t)); +} + +size_t MutationBlock::MemoryUsageForMutationBlock(void) const +{ + // includes the usage counted by MemoryUsageForFreeMutations() + return capacity_ * sizeof(Mutation); +} + +size_t MutationBlock::MemoryUsageForFreeMutations(void) const +{ + size_t mut_count = 0; + MutationIndex nextFreeBlock = free_index_; + + while (nextFreeBlock != -1) + { + mut_count++; + nextFreeBlock = *(MutationIndex *)(mutation_buffer_ + nextFreeBlock); + } + + return mut_count * sizeof(Mutation); +} + +size_t MutationBlock::MemoryUsageForMutationRefcounts(void) const +{ + return capacity_ * sizeof(slim_refcount_t); +} + +size_t MutationBlock::MemoryUsageForTraitInfo(void) const +{ + return capacity_ * (sizeof(MutationTraitInfo) * trait_count_); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/mutation_block.h b/core/mutation_block.h new file mode 100644 index 00000000..4f1396a6 --- /dev/null +++ b/core/mutation_block.h @@ -0,0 +1,148 @@ +// +// mutation_block.h +// SLiM +// +// Created by Ben Haller on 10/14/25. +// Copyright (c) 2025 Benjamin C. Haller. All rights reserved. +// A product of the Messer Lab, http://messerlab.org/slim/ +// + +// This file is part of SLiM. +// +// SLiM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// SLiM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with SLiM. If not, see . + +/* + + The class MutationBlock represents an allocation zone for Mutation objects and associated data. Each allocated mutation + is referenced by its uint32_t index into the block. Several malloced buffers are maintained by MutationBlock in parallel. + One holds the Mutation objects themselves. Another holds refcounts for the mutations, which are best kept separately for + greater memory locality during tasks that are centered on refcounts. A third holds per-trait data for each mutation; + since the number of traits is determined at runtime, the size of each record in that buffer is determined at runtime, and + so that data cannot be kept within the Mutation objects themselves. MutationBlock keeps all this in sync, reallocs all + the blocks as needed, etc. + + */ + +#ifndef __SLiM__mutation_block__ +#define __SLiM__mutation_block__ + + +#include "mutation.h" + +class Mutation; +class MutationRun; + + +class MutationBlock +{ +public: + Species &species_; + + Mutation *mutation_buffer_ = nullptr; + slim_refcount_t *refcount_buffer_ = nullptr; + MutationTraitInfo *trait_info_buffer_ = nullptr; + + MutationIndex capacity_ = 0; + MutationIndex free_index_ = -1; + MutationIndex last_used_index_ = -1; + + int trait_count_; // the number of MutationTraitInfo records kept in trait_info_buffer_ for each mutation + +#ifdef DEBUG_LOCKS_ENABLED + // We do not arbitrate access to the mutation block with a lock; instead, we expect that clients + // will manage their own multithreading issues. In DEBUG mode we check for incorrect uses (races). + // We use this lock to check. Any failure to acquire the lock indicates a race. + EidosDebugLock mutation_block_LOCK("mutation_block_LOCK"); +#endif + + explicit MutationBlock(Species &p_species, int p_trait_count); + + void IncreaseMutationBlockCapacity(void); + void ZeroRefcountBlock(MutationRun &p_mutation_registry); + + inline __attribute__((always_inline)) Mutation *MutationForIndex(MutationIndex p_index) const { return mutation_buffer_ + p_index; } + inline __attribute__((always_inline)) slim_refcount_t RefcountForIndex(MutationIndex p_index) const { return refcount_buffer_[p_index]; } + inline __attribute__((always_inline)) MutationTraitInfo *TraitInfoIndex(MutationIndex p_index) const { return trait_info_buffer_ + (p_index * trait_count_); } + + inline __attribute__((always_inline)) MutationIndex IndexInBlock(const Mutation *p_mutation) const + { + return (MutationIndex)(p_mutation - mutation_buffer_); + } + + size_t MemoryUsageForMutationBlock(void) const; + size_t MemoryUsageForFreeMutations(void) const; + size_t MemoryUsageForMutationRefcounts(void) const; + size_t MemoryUsageForTraitInfo(void) const; + + inline __attribute__((always_inline)) MutationIndex NewMutationFromBlock(void) + { + #ifdef DEBUG_LOCKS_ENABLED + mutation_block_LOCK.start_critical(0); + #endif + + if (free_index_ == -1) + IncreaseMutationBlockCapacity(); + + MutationIndex result = free_index_; + + free_index_ = *(MutationIndex *)(mutation_buffer_ + result); + + if (last_used_index_ < result) + last_used_index_ = result; + + #ifdef DEBUG_LOCKS_ENABLED + mutation_block_LOCK.end_critical(); + #endif + + return result; // no need to zero out the memory, we are just an allocater, not a constructor + } + + inline __attribute__((always_inline)) void DisposeMutationToBlock(MutationIndex p_mutation_index) + { + THREAD_SAFETY_IN_ACTIVE_PARALLEL("SLiM_DisposeMutationToBlock(): gSLiM_Mutation_Block change"); + + void *mut_ptr = mutation_buffer_ + p_mutation_index; + + *(MutationIndex *)mut_ptr = free_index_; + free_index_ = p_mutation_index; + } +}; + +#endif /* defined(__SLiM__mutation_block__) */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/mutation_run.cpp b/core/mutation_run.cpp index af95d7c3..dd6e0c16 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -19,6 +19,8 @@ #include "mutation_run.h" +#include "species.h" +#include "mutation_block.h" #include @@ -67,11 +69,12 @@ bool MutationRun::contains_mutation(const Mutation *p_mut) const // binary search bool MutationRun::contains_mutation(const Mutation *p_mut) const { - MutationIndex mutation_index = p_mut->BlockIndex(); + MutationBlock *mutation_block = p_mut->mutation_type_ptr_->mutation_block_; + MutationIndex mutation_index = mutation_block->IndexInBlock(p_mut); slim_position_t position = p_mut->position_; int mut_count = size(); const MutationIndex *mut_ptr = begin_pointer_const(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; int mut_index; { @@ -147,7 +150,7 @@ bool MutationRun::contains_mutation(const Mutation *p_mut) const Mutation *MutationRun::mutation_with_type_and_position(MutationType *p_mut_type, slim_position_t p_position, slim_position_t p_last_position) const { - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = p_mut_type->mutation_block_->mutation_buffer_; int mut_count = size(); const MutationIndex *mut_ptr = begin_pointer_const(); int mut_index; @@ -255,7 +258,7 @@ Mutation *MutationRun::mutation_with_type_and_position(MutationType *p_mut_type, return nullptr; } -const std::vector *MutationRun::derived_mutation_ids_at_position(slim_position_t p_position) const +const std::vector *MutationRun::derived_mutation_ids_at_position(Mutation *p_mut_block_ptr, slim_position_t p_position) const { THREAD_SAFETY_IN_ACTIVE_PARALLEL("MutationRun::derived_mutation_ids_at_position(): usage of statics"); @@ -269,11 +272,10 @@ const std::vector *MutationRun::derived_mutation_ids_at_position(sli // but fast for the other cases, such as new SLiM-generated mutations, which are much more common. const MutationIndex *begin_ptr = begin_pointer_const(); const MutationIndex *end_ptr = end_pointer_const(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; for (const MutationIndex *mut_ptr = end_ptr - 1; mut_ptr >= begin_ptr; --mut_ptr) { - Mutation *mut = mut_block_ptr + *mut_ptr; + Mutation *mut = p_mut_block_ptr + *mut_ptr; slim_position_t mut_position = mut->position_; if (mut_position == p_position) @@ -285,7 +287,7 @@ const std::vector *MutationRun::derived_mutation_ids_at_position(sli return &return_vec; } -void MutationRun::_RemoveFixedMutations(void) +void MutationRun::_RemoveFixedMutations(Mutation *p_mut_block_ptr) { // Mutations that have fixed, and are thus targeted for removal, have had their state_ set to kFixedAndSubstituted. // That is done only when convertToSubstitution == T, so we don't need to check that flag here. @@ -295,14 +297,13 @@ void MutationRun::_RemoveFixedMutations(void) MutationIndex *haplosome_iter = mutations_; MutationIndex *haplosome_backfill_iter = nullptr; MutationIndex *haplosome_max = mutations_ + mutation_count_; - Mutation *mutation_block_ptr = gSLiM_Mutation_Block; // haplosome_iter advances through the mutation list; for each entry it hits, the entry is either fixed (skip it) or not fixed // (copy it backward to the backfill pointer). We do this with two successive loops; the first knows that no mutation has // yet been skipped, whereas the second knows that at least one mutation has been. while (haplosome_iter != haplosome_max) { - if ((mutation_block_ptr + (*haplosome_iter++))->state_ != MutationState::kFixedAndSubstituted) + if ((p_mut_block_ptr + (*haplosome_iter++))->state_ != MutationState::kFixedAndSubstituted) continue; // Fixed mutation; we want to omit it, so we skip it in haplosome_backfill_iter and transition to the second loop @@ -320,7 +321,7 @@ void MutationRun::_RemoveFixedMutations(void) { MutationIndex mutation_index = *haplosome_iter; - if ((mutation_block_ptr + mutation_index)->state_ != MutationState::kFixedAndSubstituted) + if ((p_mut_block_ptr + mutation_index)->state_ != MutationState::kFixedAndSubstituted) { // Unfixed mutation; we want to keep it, so we copy it backward and advance our backfill pointer as well as haplosome_iter *haplosome_backfill_iter = mutation_index; @@ -347,11 +348,10 @@ void MutationRun::_RemoveFixedMutations(void) } } -bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, MutationStackPolicy p_policy, int64_t p_stack_group) +bool MutationRun::_EnforceStackPolicyForAddition(Mutation *p_mut_block_ptr, slim_position_t p_position, MutationStackPolicy p_policy, int64_t p_stack_group) { MutationIndex *begin_ptr = begin_pointer(); MutationIndex *end_ptr = end_pointer(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; if (p_policy == MutationStackPolicy::kKeepFirst) { @@ -359,7 +359,7 @@ bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, Mut // We scan in reverse order, because usually we're adding mutations on the end with emplace_back() for (MutationIndex *mut_ptr = end_ptr - 1; mut_ptr >= begin_ptr; --mut_ptr) { - Mutation *mut = mut_block_ptr + *mut_ptr; + Mutation *mut = p_mut_block_ptr + *mut_ptr; slim_position_t mut_position = mut->position_; if ((mut_position == p_position) && (mut->mutation_type_ptr_->stack_group_ == p_stack_group)) @@ -378,7 +378,7 @@ bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, Mut for (MutationIndex *mut_ptr = end_ptr - 1; mut_ptr >= begin_ptr; --mut_ptr) { - Mutation *mut = mut_block_ptr + *mut_ptr; + Mutation *mut = p_mut_block_ptr + *mut_ptr; slim_position_t mut_position = mut->position_; if ((mut_position == p_position) && (mut->mutation_type_ptr_->stack_group_ == p_stack_group)) @@ -396,7 +396,7 @@ bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, Mut for ( ; mut_ptr < end_ptr; ++mut_ptr) { MutationIndex mut_index = *mut_ptr; - Mutation *mut = mut_block_ptr + mut_index; + Mutation *mut = p_mut_block_ptr + mut_index; slim_position_t mut_position = mut->position_; if ((mut_position == p_position) && (mut->mutation_type_ptr_->stack_group_ == p_stack_group)) @@ -421,14 +421,14 @@ bool MutationRun::_EnforceStackPolicyForAddition(slim_position_t p_position, Mut EIDOS_TERMINATION << "ERROR (MutationRun::_EnforceStackPolicyForAddition): (internal error) invalid policy." << EidosTerminate(); } -void MutationRun::split_run(MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const +void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const { MutationRun *first_half = NewMutationRun(p_mutrun_context); MutationRun *second_half = NewMutationRun(p_mutrun_context); int32_t second_half_start; for (second_half_start = 0; second_half_start < mutation_count_; ++second_half_start) - if ((gSLiM_Mutation_Block + mutations_[second_half_start])->position_ >= p_split_first_position) + if ((p_mut_block_ptr + mutations_[second_half_start])->position_ >= p_split_first_position) break; if (second_half_start > 0) @@ -444,7 +444,7 @@ void MutationRun::split_run(MutationRun **p_first_half, MutationRun **p_second_h #if SLIM_USE_NONNEUTRAL_CACHES -void MutationRun::cache_nonneutral_mutations_REGIME_1() const +void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const { // // Regime 1 means there are no mutationEffect() callbacks at all, so neutrality can be assessed @@ -452,19 +452,17 @@ void MutationRun::cache_nonneutral_mutations_REGIME_1() const // zero_out_nonneutral_buffer(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; - if ((mut_block_ptr + mutindex)->selection_coeff_ != 0.0) + if ((p_mut_block_ptr + mutindex)->selection_coeff_ != 0.0) add_to_nonneutral_buffer(mutindex); } } -void MutationRun::cache_nonneutral_mutations_REGIME_2() const +void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const { // // Regime 2 means the only mutationEffect() callbacks are (a) constant-effect, (b) neutral (i.e., @@ -476,13 +474,11 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2() const // zero_out_nonneutral_buffer(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; - Mutation *mutptr = mut_block_ptr + mutindex; + Mutation *mutptr = p_mut_block_ptr + mutindex; // The result of && is not order-dependent, but the first condition is checked first. // I expect many mutations would fail the first test (thus short-circuiting), whereas @@ -492,7 +488,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2() const } } -void MutationRun::cache_nonneutral_mutations_REGIME_3() const +void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const { // // Regime 3 means that there are mutationEffect() callbacks beyond the constant neutral global @@ -504,13 +500,11 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3() const // zero_out_nonneutral_buffer(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; - Mutation *mutptr = mut_block_ptr + mutindex; + Mutation *mutptr = p_mut_block_ptr + mutindex; // The result of || is not order-dependent, but the first condition is checked first. // I have reordered this to put the fast test first; or I'm guessing it's the fast test. @@ -552,7 +546,7 @@ void MutationRun::check_nonneutral_mutation_cache() const // mutation in p_mutations_to_add, with checks with enforce_stack_policy_for_addition(). The point of // this is speed: like HaplosomeCloned(), we can merge the new mutations in much faster if we do it in // bulk. Note that p_mutations_to_set and p_mutations_to_add must both be sorted by position. -void MutationRun::clear_set_and_merge(const MutationRun &p_mutations_to_set, std::vector &p_mutations_to_add) +void MutationRun::clear_set_and_merge(Mutation *p_mut_block_ptr, const MutationRun &p_mutations_to_set, std::vector &p_mutations_to_add) { // first, clear all mutations out of the receiver clear(); @@ -592,16 +586,15 @@ void MutationRun::clear_set_and_merge(const MutationRun &p_mutations_to_set, std } // then interleave mutations together, effectively setting p_mutations_to_set and then adding in p_mutations_to_add - Mutation *mut_block_ptr = gSLiM_Mutation_Block; const MutationIndex *mutation_iter = p_mutations_to_add.data(); const MutationIndex *mutation_iter_max = mutation_iter + p_mutations_to_add.size(); MutationIndex mutation_iter_mutation_index = *mutation_iter; - slim_position_t mutation_iter_pos = (mut_block_ptr + mutation_iter_mutation_index)->position_; + slim_position_t mutation_iter_pos = (p_mut_block_ptr + mutation_iter_mutation_index)->position_; const MutationIndex *parent_iter = p_mutations_to_set.begin_pointer_const(); const MutationIndex *parent_iter_max = p_mutations_to_set.end_pointer_const(); MutationIndex parent_iter_mutation_index = *parent_iter; - slim_position_t parent_iter_pos = (mut_block_ptr + parent_iter_mutation_index)->position_; + slim_position_t parent_iter_pos = (p_mut_block_ptr + parent_iter_mutation_index)->position_; // this loop runs while we are still interleaving mutations from both sources do @@ -616,12 +609,12 @@ void MutationRun::clear_set_and_merge(const MutationRun &p_mutations_to_set, std break; parent_iter_mutation_index = *parent_iter; - parent_iter_pos = (mut_block_ptr + parent_iter_mutation_index)->position_; + parent_iter_pos = (p_mut_block_ptr + parent_iter_mutation_index)->position_; } else { // we have a new mutation to add, which we know is not already present; check the stacking policy - if (enforce_stack_policy_for_addition(mutation_iter_pos, (mut_block_ptr + mutation_iter_mutation_index)->mutation_type_ptr_)) + if (enforce_stack_policy_for_addition(p_mut_block_ptr, mutation_iter_pos, (p_mut_block_ptr + mutation_iter_mutation_index)->mutation_type_ptr_)) emplace_back(mutation_iter_mutation_index); mutation_iter++; @@ -629,7 +622,7 @@ void MutationRun::clear_set_and_merge(const MutationRun &p_mutations_to_set, std break; mutation_iter_mutation_index = *mutation_iter; - mutation_iter_pos = (mut_block_ptr + mutation_iter_mutation_index)->position_; + mutation_iter_pos = (p_mut_block_ptr + mutation_iter_mutation_index)->position_; } } while (true); @@ -644,9 +637,9 @@ void MutationRun::clear_set_and_merge(const MutationRun &p_mutations_to_set, std while (mutation_iter != mutation_iter_max) { mutation_iter_mutation_index = *mutation_iter; - mutation_iter_pos = (mut_block_ptr + mutation_iter_mutation_index)->position_; + mutation_iter_pos = (p_mut_block_ptr + mutation_iter_mutation_index)->position_; - if (enforce_stack_policy_for_addition(mutation_iter_pos, (mut_block_ptr + mutation_iter_mutation_index)->mutation_type_ptr_)) + if (enforce_stack_policy_for_addition(p_mut_block_ptr, mutation_iter_pos, (p_mut_block_ptr + mutation_iter_mutation_index)->mutation_type_ptr_)) emplace_back(mutation_iter_mutation_index); mutation_iter++; diff --git a/core/mutation_run.h b/core/mutation_run.h index 09c16e04..3cde7a3b 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -470,7 +470,7 @@ class MutationRun mutation_count_ += p_copy_count; } - inline void insert_sorted_mutation(MutationIndex p_mutation_index) + inline void insert_sorted_mutation(Mutation *p_mut_block_ptr, MutationIndex p_mutation_index) { // first push it back on the end, which deals with capacity/locking issues emplace_back(p_mutation_index); @@ -480,12 +480,12 @@ class MutationRun return; // then find the proper position for it - Mutation *mut_ptr_to_insert = gSLiM_Mutation_Block + p_mutation_index; + Mutation *mut_ptr_to_insert = p_mut_block_ptr + p_mutation_index; MutationIndex *sort_position = begin_pointer(); const MutationIndex *end_position = end_pointer_const() - 1; // the position of the newly added element for ( ; sort_position != end_position; ++sort_position) - if (CompareMutations(mut_ptr_to_insert, gSLiM_Mutation_Block + *sort_position)) // if (p_mutation->position_ < (*sort_position)->position_) + if (CompareMutations(mut_ptr_to_insert, p_mut_block_ptr + *sort_position)) // if (p_mutation->position_ < (*sort_position)->position_) break; // if we got all the way to the end, then the mutation belongs at the end, so we're done @@ -541,7 +541,7 @@ class MutationRun *sort_position = p_mutation_index; }*/ - inline void insert_sorted_mutation_if_unique(MutationIndex p_mutation_index) + inline void insert_sorted_mutation_if_unique(Mutation *p_mut_block_ptr, MutationIndex p_mutation_index) { // first push it back on the end, which deals with capacity/locking issues emplace_back(p_mutation_index); @@ -551,13 +551,13 @@ class MutationRun return; // then find the proper position for it - Mutation *mut_ptr_to_insert = gSLiM_Mutation_Block + p_mutation_index; + Mutation *mut_ptr_to_insert = p_mut_block_ptr + p_mutation_index; MutationIndex *sort_position = begin_pointer(); const MutationIndex *end_position = end_pointer_const() - 1; // the position of the newly added element for ( ; sort_position != end_position; ++sort_position) { - if (CompareMutations(mut_ptr_to_insert, gSLiM_Mutation_Block + *sort_position)) // if (p_mutation->position_ < (*sort_position)->position_) + if (CompareMutations(mut_ptr_to_insert, p_mut_block_ptr + *sort_position)) // if (p_mutation->position_ < (*sort_position)->position_) { break; } @@ -580,8 +580,8 @@ class MutationRun *sort_position = p_mutation_index; } - bool _EnforceStackPolicyForAddition(slim_position_t p_position, MutationStackPolicy p_policy, int64_t p_stack_group); - inline __attribute__((always_inline)) bool enforce_stack_policy_for_addition(slim_position_t p_position, MutationType *p_mut_type_ptr); // below + bool _EnforceStackPolicyForAddition(Mutation *p_mut_block_ptr, slim_position_t p_position, MutationStackPolicy p_policy, int64_t p_stack_group); + inline __attribute__((always_inline)) bool enforce_stack_policy_for_addition(Mutation *p_mut_block_ptr, slim_position_t p_position, MutationType *p_mut_type_ptr); // below inline __attribute__((always_inline)) void copy_from_run(const MutationRun &p_source_run) { @@ -626,11 +626,11 @@ class MutationRun // this is speed: like HaplosomeCloned(), we can merge the new mutations in much faster if we do it in // bulk. Note that p_mutations_to_set and p_mutations_to_add must both be sorted by position, and it // must be guaranteed that none of the mutations in the two given runs are the same. - void clear_set_and_merge(const MutationRun &p_mutations_to_set, std::vector &p_mutations_to_add); + void clear_set_and_merge(Mutation *p_mut_block_ptr, const MutationRun &p_mutations_to_set, std::vector &p_mutations_to_add); // This is used by the tree sequence recording code to get the full derived state at a given position. // Note that the vector returned is cached internally and reused with each call, for speed. - const std::vector *derived_mutation_ids_at_position(slim_position_t p_position) const; + const std::vector *derived_mutation_ids_at_position(Mutation *p_mut_block_ptr, slim_position_t p_position) const; inline __attribute__((always_inline)) const MutationIndex *begin_pointer_const(void) const { @@ -652,14 +652,14 @@ class MutationRun return mutations_ + mutation_count_; } - void _RemoveFixedMutations(void); - inline __attribute__((always_inline)) void RemoveFixedMutations(int64_t p_operation_id) + void _RemoveFixedMutations(Mutation *p_mut_block_ptr); + inline __attribute__((always_inline)) void RemoveFixedMutations(Mutation *p_mut_block_ptr, int64_t p_operation_id) { if (operation_id_ != p_operation_id) { operation_id_ = p_operation_id; - _RemoveFixedMutations(); + _RemoveFixedMutations(p_mut_block_ptr); } } @@ -694,7 +694,7 @@ class MutationRun } // splitting mutation runs - void split_run(MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const; + void split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const; #if SLIM_USE_NONNEUTRAL_CACHES // caching non-neutral mutations; see above for comments about the "regime" etc. @@ -737,13 +737,13 @@ class MutationRun ++nonneutral_mutations_count_; } - void cache_nonneutral_mutations_REGIME_1() const; - void cache_nonneutral_mutations_REGIME_2() const; - void cache_nonneutral_mutations_REGIME_3() const; + void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const; + void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const; + void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const; void check_nonneutral_mutation_cache() const; - inline __attribute__((always_inline)) void beginend_nonneutral_pointers(const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const + inline __attribute__((always_inline)) void beginend_nonneutral_pointers(Mutation *p_mut_block_ptr, const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const { if ((nonneutral_change_validation_ != p_nonneutral_change_counter) || (nonneutral_mutations_count_ == -1)) { @@ -757,9 +757,9 @@ class MutationRun switch (p_nonneutral_regime) { - case 1: cache_nonneutral_mutations_REGIME_1(); break; - case 2: cache_nonneutral_mutations_REGIME_2(); break; - case 3: cache_nonneutral_mutations_REGIME_3(); break; + case 1: cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr); break; + case 2: cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr); break; + case 3: cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr); break; } #if (SLIMPROFILING == 1) @@ -838,7 +838,7 @@ class MutationRun // We need MutationType below, but we can't include it at top because it requires MutationRun to be defined... #include "mutation_type.h" -inline __attribute__((always_inline)) bool MutationRun::enforce_stack_policy_for_addition(slim_position_t p_position, MutationType *p_mut_type_ptr) +inline __attribute__((always_inline)) bool MutationRun::enforce_stack_policy_for_addition(Mutation *p_mut_block_ptr, slim_position_t p_position, MutationType *p_mut_type_ptr) { MutationStackPolicy policy = p_mut_type_ptr->stack_policy_; @@ -850,7 +850,7 @@ inline __attribute__((always_inline)) bool MutationRun::enforce_stack_policy_for else { // Otherwise, a relatively complicated check is needed, so we call out to a non-inline function - return _EnforceStackPolicyForAddition(p_position, policy, p_mut_type_ptr->stack_group_); + return _EnforceStackPolicyForAddition(p_mut_block_ptr, p_position, policy, p_mut_type_ptr->stack_group_); } } diff --git a/core/mutation_type.h b/core/mutation_type.h index 0676cdea..3af09600 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -41,6 +41,7 @@ #include "slim_globals.h" class Species; +class MutationBlock; extern EidosClass *gSLiM_MutationType_Class; @@ -92,6 +93,7 @@ class MutationType : public EidosDictionaryUnretained // examples: synonymous, nonsynonymous, adaptive, etc. Species &species_; + MutationBlock *mutation_block_ = nullptr; // NOT OWNED; a pointer to the MutationBlock from the species slim_objectid_t mutation_type_id_; // the id by which this mutation type is indexed in the chromosome EidosValue_SP cached_value_muttype_id_; // a cached value for mutation_type_id_; reset() if that changes diff --git a/core/population.cpp b/core/population.cpp index dfc55a9a..ffa8885d 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -37,6 +37,7 @@ #include "polymorphism.h" #include "subpopulation.h" #include "interaction_type.h" +#include "mutation_block.h" #include "eidos_globals.h" #if EIDOS_ROBIN_HOOD_HASHING @@ -114,15 +115,19 @@ void Population::RemoveAllSubpopulationInfo(void) // The malloced storage of the mutation registry will be freed when it is destroyed, but it // does not know that the Mutation pointers inside it are owned, so we need to release them. - Mutation *mut_block_ptr = gSLiM_Mutation_Block; int registry_size; const MutationIndex *registry_iter = MutationRegistry(®istry_size); const MutationIndex *registry_iter_end = registry_iter + registry_size; - for (; registry_iter != registry_iter_end; ++registry_iter) - (mut_block_ptr + *registry_iter)->Release(); - - mutation_registry_.clear(); + if (registry_size) + { + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; + + for (; registry_iter != registry_iter_end; ++registry_iter) + (mut_block_ptr + *registry_iter)->Release(); + + mutation_registry_.clear(); + } #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES // If we're keeping any separate registries inside mutation types, clear those now as well @@ -2992,7 +2997,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h // no mutations, but we do have crossovers, so we just need to interleave the two parental haplosomes // - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; Haplosome *parent_haplosome = parent_haplosome_1; slim_position_t mutrun_length = p_child_haplosome.mutrun_length_; int mutrun_count = p_child_haplosome.mutrun_count_; @@ -3179,7 +3184,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h } #endif - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; const MutationIndex *mutation_iter = mutations_to_add.data(); const MutationIndex *mutation_iter_max = mutation_iter + mutations_to_add.size(); @@ -3249,7 +3254,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h Mutation *new_mut = mut_block_ptr + mutation_iter_mutation_index; MutationType *new_mut_type = new_mut->mutation_type_ptr_; - if (child_mutrun->enforce_stack_policy_for_addition(new_mut->position_, new_mut_type)) + if (child_mutrun->enforce_stack_policy_for_addition(mut_block_ptr, new_mut->position_, new_mut_type)) { // The mutation was passed by the stacking policy, so we can add it to the child haplosome and the registry child_mutrun->emplace_back(mutation_iter_mutation_index); @@ -3267,7 +3272,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h { #pragma omp critical (TreeSeqNewDerivedState) { - species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(new_mut->position_)); + species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(mut_block_ptr, new_mut->position_)); } } } @@ -3399,7 +3404,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h Mutation *new_mut = mut_block_ptr + mutation_iter_mutation_index; MutationType *new_mut_type = new_mut->mutation_type_ptr_; - if (child_mutrun->enforce_stack_policy_for_addition(new_mut->position_, new_mut_type)) + if (child_mutrun->enforce_stack_policy_for_addition(mut_block_ptr, new_mut->position_, new_mut_type)) { // The mutation was passed by the stacking policy, so we can add it to the child haplosome and the registry child_mutrun->emplace_back(mutation_iter_mutation_index); @@ -3417,7 +3422,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h { #pragma omp critical (TreeSeqNewDerivedState) { - species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(new_mut->position_)); + species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(mut_block_ptr, new_mut->position_)); } } } @@ -3453,7 +3458,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h Mutation *new_mut = mut_block_ptr + mutation_iter_mutation_index; MutationType *new_mut_type = new_mut->mutation_type_ptr_; - if (child_mutrun->enforce_stack_policy_for_addition(new_mut->position_, new_mut_type)) + if (child_mutrun->enforce_stack_policy_for_addition(mut_block_ptr, new_mut->position_, new_mut_type)) { // The mutation was passed by the stacking policy, so we can add it to the child haplosome and the registry child_mutrun->emplace_back(mutation_iter_mutation_index); @@ -3471,7 +3476,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h { #pragma omp critical (TreeSeqNewDerivedState) { - species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(new_mut->position_)); + species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(mut_block_ptr, new_mut->position_)); } } } @@ -3608,7 +3613,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h Mutation *new_mut = mut_block_ptr + mutation_iter_mutation_index; MutationType *new_mut_type = new_mut->mutation_type_ptr_; - if (child_mutrun->enforce_stack_policy_for_addition(new_mut->position_, new_mut_type)) + if (child_mutrun->enforce_stack_policy_for_addition(mut_block_ptr, new_mut->position_, new_mut_type)) { // The mutation was passed by the stacking policy, so we can add it to the child haplosome and the registry child_mutrun->emplace_back(mutation_iter_mutation_index); @@ -3626,7 +3631,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h { #pragma omp critical (TreeSeqNewDerivedState) { - species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(new_mut->position_)); + species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(mut_block_ptr, new_mut->position_)); } } } @@ -3830,7 +3835,7 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha } // loop over mutation runs and either (1) copy the mutrun pointer from the parent, or (2) make a new mutrun by modifying that of the parent - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; int mutrun_count = p_child_haplosome.mutrun_count_; slim_position_t mutrun_length = p_child_haplosome.mutrun_length_; @@ -3879,7 +3884,7 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha Mutation *new_mut = mut_block_ptr + mutation_iter_mutation_index; MutationType *new_mut_type = new_mut->mutation_type_ptr_; - if (child_run->enforce_stack_policy_for_addition(mutation_iter_pos, new_mut_type)) + if (child_run->enforce_stack_policy_for_addition(mut_block_ptr, mutation_iter_pos, new_mut_type)) { // The mutation was passed by the stacking policy, so we can add it to the child haplosome and the registry child_run->emplace_back(mutation_iter_mutation_index); @@ -3897,7 +3902,7 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha { #pragma omp critical (TreeSeqNewDerivedState) { - species_.RecordNewDerivedState(&p_child_haplosome, mutation_iter_pos, *child_run->derived_mutation_ids_at_position(mutation_iter_pos)); + species_.RecordNewDerivedState(&p_child_haplosome, mutation_iter_pos, *child_run->derived_mutation_ids_at_position(mut_block_ptr, mutation_iter_pos)); } } } @@ -4041,7 +4046,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil // no mutations, but we do have crossovers, so we just need to interleave the two parental haplosomes // - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; Haplosome *parent_haplosome = parent_haplosome_1; slim_position_t mutrun_length = p_child_haplosome.mutrun_length_; int mutrun_count = p_child_haplosome.mutrun_count_; @@ -4223,7 +4228,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil } #endif - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; const MutationIndex *mutation_iter = mutations_to_add.data(); const MutationIndex *mutation_iter_max = mutation_iter + mutations_to_add.size(); @@ -4338,7 +4343,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil Mutation *new_mut = mut_block_ptr + mutation_iter_mutation_index; MutationType *new_mut_type = new_mut->mutation_type_ptr_; - if (child_mutrun->enforce_stack_policy_for_addition(new_mut->position_, new_mut_type)) + if (child_mutrun->enforce_stack_policy_for_addition(mut_block_ptr, new_mut->position_, new_mut_type)) { // The mutation was passed by the stacking policy, so we can add it to the child haplosome and the registry child_mutrun->emplace_back(mutation_iter_mutation_index); @@ -4356,7 +4361,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil { #pragma omp critical (TreeSeqNewDerivedState) { - species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(new_mut->position_)); + species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(mut_block_ptr, new_mut->position_)); } } } @@ -4392,7 +4397,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil Mutation *new_mut = mut_block_ptr + mutation_iter_mutation_index; MutationType *new_mut_type = new_mut->mutation_type_ptr_; - if (child_mutrun->enforce_stack_policy_for_addition(new_mut->position_, new_mut_type)) + if (child_mutrun->enforce_stack_policy_for_addition(mut_block_ptr, new_mut->position_, new_mut_type)) { // The mutation was passed by the stacking policy, so we can add it to the child haplosome and the registry child_mutrun->emplace_back(mutation_iter_mutation_index); @@ -4410,7 +4415,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil { #pragma omp critical (TreeSeqNewDerivedState) { - species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(new_mut->position_)); + species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(mut_block_ptr, new_mut->position_)); } } } @@ -4547,7 +4552,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil Mutation *new_mut = mut_block_ptr + mutation_iter_mutation_index; MutationType *new_mut_type = new_mut->mutation_type_ptr_; - if (child_mutrun->enforce_stack_policy_for_addition(new_mut->position_, new_mut_type)) + if (child_mutrun->enforce_stack_policy_for_addition(mut_block_ptr, new_mut->position_, new_mut_type)) { // The mutation was passed by the stacking policy, so we can add it to the child haplosome and the registry child_mutrun->emplace_back(mutation_iter_mutation_index); @@ -4565,7 +4570,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil { #pragma omp critical (TreeSeqNewDerivedState) { - species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(new_mut->position_)); + species_.RecordNewDerivedState(&p_child_haplosome, new_mut->position_, *child_mutrun->derived_mutation_ids_at_position(mut_block_ptr, new_mut->position_)); } } } @@ -4894,12 +4899,13 @@ void Population::DoHeteroduplexRepair(std::vector &p_heterodupl // might have been newly added at a position, and then removed again by mismatch repair; // we will need to make sure that the recorded state is correct when that occurs. + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; + if ((repair_removals.size() > 0) || (repair_additions.size() > 0)) { // We loop through the mutation runs in p_child_haplosome, and for each one, if there are // mutations to be added or removed we make a new mutation run and effect the changes // as we copy mutations over. Mutruns without changes are left untouched. - Mutation *mut_block_ptr = gSLiM_Mutation_Block; slim_position_t mutrun_length = p_child_haplosome->mutrun_length_; slim_position_t mutrun_count = p_child_haplosome->mutrun_count_; std::size_t removal_index = 0, addition_index = 0; @@ -5000,7 +5006,7 @@ void Population::DoHeteroduplexRepair(std::vector &p_heterodupl { #pragma omp critical (TreeSeqNewDerivedState) { - species_.RecordNewDerivedState(p_child_haplosome, changed_pos, *p_child_haplosome->derived_mutation_ids_at_position(changed_pos)); + species_.RecordNewDerivedState(p_child_haplosome, changed_pos, *p_child_haplosome->derived_mutation_ids_at_position(mut_block_ptr, changed_pos)); } } } @@ -5186,7 +5192,7 @@ void Population::AddTallyForMutationTypeAndBinNumber(int p_mutation_type_index, void Population::ValidateMutationFitnessCaches(void) { - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; int registry_size; const MutationIndex *registry_iter = MutationRegistry(®istry_size); const MutationIndex *registry_iter_end = registry_iter + registry_size; @@ -5751,6 +5757,8 @@ void Population::SplitMutationRunsForChromosome(int32_t p_new_mutrun_count, Chro if (!mutruns_buf) EIDOS_TERMINATION << "ERROR (Population::SplitMutationRuns): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; + try { // for every subpop for (const std::pair &subpop_pair : subpops_) @@ -5786,7 +5794,7 @@ void Population::SplitMutationRunsForChromosome(int32_t p_new_mutrun_count, Chro // checking use_count() this way is only safe because we run directly after tallying! MutationRun *first_half, *second_half; - mutrun->split_run(&first_half, &second_half, new_mutrun_length * (mutruns_buf_index + 1), mutrun_context); + mutrun->split_run(mut_block_ptr, &first_half, &second_half, new_mutrun_length * (mutruns_buf_index + 1), mutrun_context); mutruns_buf[mutruns_buf_index++] = first_half; mutruns_buf[mutruns_buf_index++] = second_half; @@ -5811,7 +5819,7 @@ void Population::SplitMutationRunsForChromosome(int32_t p_new_mutrun_count, Chro // it was not in the map, so make the new runs, and insert them into the map MutationRun *first_half, *second_half; - mutrun->split_run(&first_half, &second_half, new_mutrun_length * (mutruns_buf_index + 1), mutrun_context); + mutrun->split_run(mut_block_ptr, &first_half, &second_half, new_mutrun_length * (mutruns_buf_index + 1), mutrun_context); mutruns_buf[mutruns_buf_index++] = first_half; mutruns_buf[mutruns_buf_index++] = second_half; @@ -6061,6 +6069,39 @@ void Population::JoinMutationRunsForChromosome(int32_t p_new_mutrun_count, Chrom } #endif +void Population::MutationRegistryAdd(Mutation *p_mutation) +{ +#if DEBUG + if ((p_mutation->state_ == MutationState::kInRegistry) || + (p_mutation->state_ == MutationState::kRemovedWithSubstitution) || + (p_mutation->state_ == MutationState::kFixedAndSubstituted)) + EIDOS_TERMINATION << "ERROR (Population::MutationRegistryAdd): " << "(internal error) cannot add a mutation to the registry that is already in the registry, or has been fixed/substituted." << EidosTerminate(); +#endif + + // We could be adding a lost mutation back into the registry (from a mutation() callback), in which case it gets a retain + // New mutations already have a retain count of 1, which we use (i.e., we take ownership of the mutation passed in to us) + if (p_mutation->state_ != MutationState::kNewMutation) + p_mutation->Retain(); + + MutationIndex new_mut_index = mutation_block_->IndexInBlock(p_mutation); + mutation_registry_.emplace_back(new_mut_index); + + p_mutation->state_ = MutationState::kInRegistry; + +#ifdef SLIM_KEEP_MUTTYPE_REGISTRIES + if (keeping_muttype_registries_) + { + MutationType *mutation_type_ptr = p_mutation->mutation_type_ptr_; + + if (mutation_type_ptr->keeping_muttype_registry_) + { + // This mutation type is also keeping its own private registry, so we need to add to that as well + mutation_type_ptr->muttype_registry_.emplace_back(new_mut_index); + } + } +#endif +} + // Tally mutations and remove fixed/lost mutations void Population::MaintainMutationRegistry(void) { @@ -6129,7 +6170,7 @@ void Population::AssessMutationRuns(void) { slim_chromosome_index_t chromosome_index = chromosome->Index(); int registry_size = 0, registry_count_in_chromosome = 0; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; const MutationIndex *registry = MutationRegistry(®istry_size); for (int registry_index = 0; registry_index < registry_size; ++registry_index) @@ -6941,8 +6982,9 @@ void Population::TallyMutationReferencesAcrossPopulation_SLiMgui(void) } // Then copy the tallied refcounts into our private refcounts - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; + MutationBlock *mutation_block = mutation_block_; + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + slim_refcount_t *refcount_block_ptr = mutation_block->refcount_buffer_; int registry_size; const MutationIndex *registry_iter = MutationRegistry(®istry_size); const MutationIndex *registry_iter_end = registry_iter + registry_size; @@ -6976,7 +7018,8 @@ void Population::TallyMutationReferencesAcrossSubpopulations(std::vectorrefcount_buffer_; // We have two ways of tallying; here we decide which way to use. We only loop through haplosomes // if we are tallying for a single subpopulation and it is small; otherwise, looping through @@ -7013,7 +7056,7 @@ void Population::TallyMutationReferencesAcrossSubpopulations(std::vector 1); + mutation_block->ZeroRefcountBlock(mutation_registry_); for (Chromosome *chromosome : species_.Chromosomes()) chromosome->tallied_haplosome_count_ = 0; @@ -7082,7 +7125,8 @@ void Population::TallyMutationReferencesAcrossSubpopulations(std::vectorrefcount_buffer_; // We have two ways of tallying; here we decide which way to use. We tally directly by // looping through haplosomes below a certain problem threshold, because there is some @@ -7108,7 +7152,7 @@ void Population::TallyMutationReferencesAcrossHaplosomes(const Haplosome * const else { // SLOW PATH: Increment the refcounts through all pointers to Mutation in all haplosomes - SLiM_ZeroRefcountBlock(mutation_registry_, /* p_registry_only */ community_.AllSpecies().size() > 1); + mutation_block->ZeroRefcountBlock(mutation_registry_); for (Chromosome *chromosome : species_.Chromosomes()) chromosome->tallied_haplosome_count_ = 0; @@ -7152,7 +7196,10 @@ void Population::TallyMutationReferencesAcrossHaplosomes(const Haplosome * const void Population::_TallyMutationReferences_FAST_FromMutationRunUsage(bool p_clock_for_mutrun_experiments) { // first zero out the refcounts in all registered Mutation objects - SLiM_ZeroRefcountBlock(mutation_registry_, /* p_registry_only */ community_.AllSpecies().size() > 1); + MutationBlock *mutation_block = mutation_block_; + slim_refcount_t *refcount_block_ptr = mutation_block->refcount_buffer_; + + mutation_block->ZeroRefcountBlock(mutation_registry_); for (Chromosome *chromosome : species_.Chromosomes()) { @@ -7169,7 +7216,6 @@ void Population::_TallyMutationReferences_FAST_FromMutationRunUsage(bool p_clock MutationRunContext &mutrun_context = chromosome->ChromosomeMutationRunContextForThread(omp_get_thread_num()); MutationRunPool &inuse_pool = mutrun_context.in_use_pool_; size_t inuse_pool_count = inuse_pool.size(); - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; for (size_t pool_index = 0; pool_index < inuse_pool_count; ++pool_index) { @@ -7228,7 +7274,9 @@ void Population::_CheckMutationTallyAcrossHaplosomes(const Haplosome * const *ha // It should be called immediately after tallying, and passed a vector of the haplosomes tallied across. int registry_count; const MutationIndex *registry_iter = MutationRegistry(®istry_count); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + MutationBlock *mutation_block = mutation_block_; + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + slim_refcount_t *refcount_block_ptr = mutation_block->refcount_buffer_; // zero out all check refcounts for (int registry_index = 0; registry_index < registry_count; ++registry_index) @@ -7255,8 +7303,6 @@ void Population::_CheckMutationTallyAcrossHaplosomes(const Haplosome * const *ha } // then loop through the registry and check that all check refcounts match tallied refcounts - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; - for (int registry_index = 0; registry_index < registry_count; ++registry_index) { MutationIndex mut_blockindex = registry_iter[registry_index]; @@ -7276,7 +7322,9 @@ void Population::_CheckMutationTallyAcrossHaplosomes(const Haplosome * const *ha EidosValue_SP Population::Eidos_FrequenciesForTalliedMutations(EidosValue *mutations_value) { - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; + MutationBlock *mutation_block = mutation_block_; + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + slim_refcount_t *refcount_block_ptr = mutation_block->refcount_buffer_; EidosValue_SP result_SP; // Fetch tallied haplosome counts for all chromosomes up front; these will be set up beforehand @@ -7310,7 +7358,7 @@ EidosValue_SP Population::Eidos_FrequenciesForTalliedMutations(EidosValue *mutat int8_t mut_state = mut->state_; double freq; - if (mut_state == MutationState::kInRegistry) freq = *(refcount_block_ptr + mut->BlockIndex()) / tallied_haplosome_counts[mut->chromosome_index_]; + if (mut_state == MutationState::kInRegistry) freq = *(refcount_block_ptr + mutation_block->IndexInBlock(mut)) / tallied_haplosome_counts[mut->chromosome_index_]; else if (mut_state == MutationState::kLostAndRemoved) freq = 0.0; else freq = 1.0; @@ -7323,7 +7371,6 @@ EidosValue_SP Population::Eidos_FrequenciesForTalliedMutations(EidosValue *mutat // this is the same as the case below, except MutationState::kRemovedWithSubstitution is possible int registry_size; const MutationIndex *registry = MutationRegistry(®istry_size); - Mutation *mutation_block_ptr = gSLiM_Mutation_Block; EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(registry_size); result_SP = EidosValue_SP(float_result); @@ -7331,7 +7378,7 @@ EidosValue_SP Population::Eidos_FrequenciesForTalliedMutations(EidosValue *mutat for (int registry_index = 0; registry_index < registry_size; registry_index++) { MutationIndex mut_index = registry[registry_index]; - const Mutation *mut = mutation_block_ptr + mut_index; + const Mutation *mut = mut_block_ptr + mut_index; double freq; if (mut->state_ == MutationState::kInRegistry) freq = *(refcount_block_ptr + mut_index) / tallied_haplosome_counts[mut->chromosome_index_]; @@ -7345,7 +7392,6 @@ EidosValue_SP Population::Eidos_FrequenciesForTalliedMutations(EidosValue *mutat // no mutation vector was given, so return all frequencies from the registry int registry_size; const MutationIndex *registry = MutationRegistry(®istry_size); - Mutation *mutation_block_ptr = gSLiM_Mutation_Block; EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(registry_size); result_SP = EidosValue_SP(float_result); @@ -7353,7 +7399,7 @@ EidosValue_SP Population::Eidos_FrequenciesForTalliedMutations(EidosValue *mutat for (int registry_index = 0; registry_index < registry_size; registry_index++) { MutationIndex mut_index = registry[registry_index]; - const Mutation *mut = mutation_block_ptr + mut_index; + const Mutation *mut = mut_block_ptr + mut_index; float_result->set_float_no_check(*(refcount_block_ptr + registry[registry_index]) / tallied_haplosome_counts[mut->chromosome_index_], registry_index); } } @@ -7363,7 +7409,9 @@ EidosValue_SP Population::Eidos_FrequenciesForTalliedMutations(EidosValue *mutat EidosValue_SP Population::Eidos_CountsForTalliedMutations(EidosValue *mutations_value) { - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; + MutationBlock *mutation_block = mutation_block_; + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + slim_refcount_t *refcount_block_ptr = mutation_block->refcount_buffer_; EidosValue_SP result_SP; // Fetch total haplosome counts for all chromosomes up front; these will be set up beforehand @@ -7397,7 +7445,7 @@ EidosValue_SP Population::Eidos_CountsForTalliedMutations(EidosValue *mutations_ int8_t mut_state = mut->state_; slim_refcount_t count; - if (mut_state == MutationState::kInRegistry) count = *(refcount_block_ptr + mut->BlockIndex()); + if (mut_state == MutationState::kInRegistry) count = *(refcount_block_ptr + mutation_block->IndexInBlock(mut)); else if (mut_state == MutationState::kLostAndRemoved) count = 0; else count = tallied_haplosome_counts[mut->chromosome_index_]; @@ -7410,7 +7458,6 @@ EidosValue_SP Population::Eidos_CountsForTalliedMutations(EidosValue *mutations_ // this is the same as the case below, except MutationState::kRemovedWithSubstitution is possible int registry_size; const MutationIndex *registry = MutationRegistry(®istry_size); - Mutation *mutation_block_ptr = gSLiM_Mutation_Block; EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(registry_size); result_SP = EidosValue_SP(int_result); @@ -7418,7 +7465,7 @@ EidosValue_SP Population::Eidos_CountsForTalliedMutations(EidosValue *mutations_ for (int registry_index = 0; registry_index < registry_size; registry_index++) { MutationIndex mut_index = registry[registry_index]; - const Mutation *mut = mutation_block_ptr + mut_index; + const Mutation *mut = mut_block_ptr + mut_index; slim_refcount_t count; if (mut->state_ == MutationState::kInRegistry) count = *(refcount_block_ptr + mut_index); @@ -7470,8 +7517,9 @@ void Population::RemoveAllFixedMutations(void) total_haplosome_counts.push_back(chromosome->total_haplosome_count_); // remove Mutation objects that are no longer referenced, freeing them; avoid using an iterator since it would be invalidated - slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + MutationBlock *mutation_block = mutation_block_; + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + slim_refcount_t *refcount_block_ptr = mutation_block->refcount_buffer_; { int registry_size; @@ -7668,7 +7716,7 @@ void Population::RemoveAllFixedMutations(void) slim_position_t mut_position = mutation->position_; slim_mutrun_index_t mutrun_index = (slim_mutrun_index_t)(mut_position / mutrun_length); - haplosome->RemoveFixedMutations(operation_id, mutrun_index); + haplosome->RemoveFixedMutations(mut_block_ptr, operation_id, mutrun_index); } } } @@ -7747,7 +7795,7 @@ void Population::CheckMutationRegistry(bool p_check_haplosomes) if ((model_type_ == SLiMModelType::kModelTypeWF) && child_generation_valid_) EIDOS_TERMINATION << "ERROR (Population::CheckMutationRegistry): (internal error) CheckMutationRegistry() may only be called from the parent generation in WF models." << EidosTerminate(); - Mutation *mutation_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; #if DEBUG_MUTATION_ZOMBIES slim_refcount_t *refcount_block_ptr = gSLiM_Mutation_Refcounts; #endif @@ -7767,7 +7815,7 @@ void Population::CheckMutationRegistry(bool p_check_haplosomes) EIDOS_TERMINATION << "ERROR (Population::CheckMutationRegistry): (internal error) zombie mutation found in registry with address " << (*registry_iter) << EidosTerminate(); #endif - int8_t mut_state = (mutation_block_ptr + mut_index)->state_; + int8_t mut_state = (mut_block_ptr + mut_index)->state_; if (mut_state != MutationState::kInRegistry) EIDOS_TERMINATION << "ERROR (Population::CheckMutationRegistry): A mutation was found in the mutation registry with a state other than MutationState::kInRegistry (" << (int)mut_state << "). This may be the result of calling removeMutations(substitute=T) without actually removing the mutation from all haplosomes." << EidosTerminate(); @@ -7813,7 +7861,7 @@ void Population::CheckMutationRegistry(bool p_check_haplosomes) EIDOS_TERMINATION << "ERROR (Population::CheckMutationRegistry): (internal error) zombie mutation found in haplosome with address " << (*haplosome_iter) << EidosTerminate(); #endif - int8_t mut_state = (mutation_block_ptr + mut_index)->state_; + int8_t mut_state = (mut_block_ptr + mut_index)->state_; if (mut_state != MutationState::kInRegistry) EIDOS_TERMINATION << "ERROR (Population::CheckMutationRegistry): A mutation was found in a haplosome with a state other than MutationState::kInRegistry (" << (int)mut_state << "). This may be the result of calling removeMutations(substitute=T) without actually removing the mutation from all haplosomes." << EidosTerminate(); @@ -8085,7 +8133,7 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit int first_haplosome_index = species_.FirstHaplosomeIndices()[chromosome_index]; int last_haplosome_index = species_.LastHaplosomeIndices()[chromosome_index]; PolymorphismMap polymorphisms; - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; for (const std::pair &subpop_pair : subpops_) // go through all subpopulations { @@ -8375,7 +8423,7 @@ void Population::PrintSample_SLiM(std::ostream &p_out, Subpopulation &p_subpop, } // print the sample using Haplosome's static member function - Haplosome::PrintHaplosomes_SLiM(p_out, sample, /* p_output_object_tags */ false); + Haplosome::PrintHaplosomes_SLiM(p_out, species_, sample, /* p_output_object_tags */ false); } // print sample of p_sample_size haplosomes from subpopulation p_subpop_id, using "ms" format @@ -8429,7 +8477,7 @@ void Population::PrintSample_MS(std::ostream &p_out, Subpopulation &p_subpop, sl } // print the sample using Haplosome's static member function - Haplosome::PrintHaplosomes_MS(p_out, sample, p_chromosome, p_filter_monomorphic); + Haplosome::PrintHaplosomes_MS(p_out, species_, sample, p_chromosome, p_filter_monomorphic); } // print sample of p_sample_size *individuals* (NOT haplosomes or genomes) from subpopulation p_subpop_id diff --git a/core/population.h b/core/population.h index 076ff991..cf9a5f42 100644 --- a/core/population.h +++ b/core/population.h @@ -134,6 +134,7 @@ class Population SLiMModelType model_type_; Community &community_; Species &species_; + MutationBlock *mutation_block_ = nullptr; // NOT OWNED; a pointer to the MutationBlock from the species // Object pools for individuals and haplosomes, kept species-wide EidosObjectPool &species_haplosome_pool_; // NOT OWNED; a pool out of which haplosomes are allocated, for within-species locality @@ -187,38 +188,7 @@ class Population return mutation_registry_.begin_pointer_const(); } - inline void MutationRegistryAdd(Mutation *p_mutation) - { -#if DEBUG - if ((p_mutation->state_ == MutationState::kInRegistry) || - (p_mutation->state_ == MutationState::kRemovedWithSubstitution) || - (p_mutation->state_ == MutationState::kFixedAndSubstituted)) - EIDOS_TERMINATION << "ERROR (Population::MutationRegistryAdd): " << "(internal error) cannot add a mutation to the registry that is already in the registry, or has been fixed/substituted." << EidosTerminate(); -#endif - - // We could be adding a lost mutation back into the registry (from a mutation() callback), in which case it gets a retain - // New mutations already have a retain count of 1, which we use (i.e., we take ownership of the mutation passed in to us) - if (p_mutation->state_ != MutationState::kNewMutation) - p_mutation->Retain(); - - MutationIndex new_mut_index = p_mutation->BlockIndex(); - mutation_registry_.emplace_back(new_mut_index); - - p_mutation->state_ = MutationState::kInRegistry; - -#ifdef SLIM_KEEP_MUTTYPE_REGISTRIES - if (keeping_muttype_registries_) - { - MutationType *mutation_type_ptr = p_mutation->mutation_type_ptr_; - - if (mutation_type_ptr->keeping_muttype_registry_) - { - // This mutation type is also keeping its own private registry, so we need to add to that as well - mutation_type_ptr->muttype_registry_.emplace_back(new_mut_index); - } - } -#endif - } + void MutationRegistryAdd(Mutation *p_mutation); // apply modifyChild() callbacks to a generated child; a return of false means "do not use this child, generate a new one" bool ApplyModifyChildCallbacks(Individual *p_child, Individual *p_parent1, Individual *p_parent2, bool p_is_selfing, bool p_is_cloning, Subpopulation *p_target_subpop, Subpopulation *p_source_subpop, std::vector &p_modify_child_callbacks); diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 83d2c155..16f3790d 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -99,9 +99,6 @@ void SLiM_WarmUp(void) for (EidosClass *eidos_class : EidosClass::RegisteredClasses(true, true)) eidos_class->CacheDispatchTables(); - // Set up our shared pool for Mutation objects - SLiM_CreateMutationBlock(); - // Configure the Eidos context information SLiM_ConfigureContext(); @@ -519,6 +516,7 @@ void SumUpMemoryUsage_Community(SLiMMemoryUsage_Community &p_usage) p_usage.totalMemoryUsage = p_usage.communityObjects + p_usage.mutationRefcountBuffer + + p_usage.mutationPerTraitBuffer + p_usage.mutationUnusedPoolSpace + p_usage.interactionTypeObjects + p_usage.interactionTypeKDTrees + @@ -596,6 +594,7 @@ void AccumulateMemoryUsageIntoTotal_Community(SLiMMemoryUsage_Community &p_usage p_total.communityObjects += p_usage.communityObjects; p_total.mutationRefcountBuffer += p_usage.mutationRefcountBuffer; + p_total.mutationPerTraitBuffer += p_usage.mutationPerTraitBuffer; p_total.mutationUnusedPoolSpace += p_usage.mutationUnusedPoolSpace; p_total.interactionTypeObjects_count += p_usage.interactionTypeObjects_count; @@ -2587,6 +2586,7 @@ void WriteProfileResults(std::string profile_output_path, std::string model_name snprintf(buf, 256, "%0.2f", mem_tot_S.mutationObjects_count / ddiv); fout << "

" << ColoredSpanForByteCount(mem_tot_S.mutationObjects / div, average_total) << " / " << ColoredSpanForByteCount(mem_last_S.mutationObjects, final_total) << " : Mutation objects (" << buf << " / " << mem_last_S.mutationObjects_count << ")
\n"; fout << "   " << ColoredSpanForByteCount(mem_tot_C.mutationRefcountBuffer / div, average_total) << " / " << ColoredSpanForByteCount(mem_last_C.mutationRefcountBuffer, final_total) << " : refcount buffer
\n"; + fout << "   " << ColoredSpanForByteCount(mem_tot_C.mutationPerTraitBuffer / div, average_total) << " / " << ColoredSpanForByteCount(mem_last_C.mutationPerTraitBuffer, final_total) << " : per-trait buffer
\n"; fout << "   " << ColoredSpanForByteCount(mem_tot_C.mutationUnusedPoolSpace / div, average_total) << " / " << ColoredSpanForByteCount(mem_last_C.mutationUnusedPoolSpace, final_total) << " : unused pool space

\n\n"; // MutationRun diff --git a/core/slim_globals.h b/core/slim_globals.h index 74a51ad8..a2a7c236 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -451,8 +451,9 @@ typedef struct int64_t communityObjects_count; size_t communityObjects; - size_t mutationRefcountBuffer; // this pool is kept globally by Mutation - size_t mutationUnusedPoolSpace; // this pool is kept globally by Mutation + size_t mutationRefcountBuffer; // this pool is kept by Species + size_t mutationPerTraitBuffer; // this pool is kept by Species + size_t mutationUnusedPoolSpace; // this pool is kept by Species int64_t interactionTypeObjects_count; // InteractionType is kept by Community now size_t interactionTypeObjects; diff --git a/core/species.cpp b/core/species.cpp index b427c491..ac5d6e7b 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -31,6 +31,7 @@ #include "polymorphism.h" #include "subpopulation.h" #include "interaction_type.h" +#include "mutation_block.h" #include "log_file.h" #include @@ -185,6 +186,20 @@ Species::~Species(void) std::fill(chromosome_for_haplosome_index_.begin(), chromosome_for_haplosome_index_.end(), nullptr); chromosome_for_haplosome_index_.clear(); + + // Free our MutationBlock, and make those with copies of it forget it; see CreateAndPromulgateMutationBlock + { + delete mutation_block_; + mutation_block_ = nullptr; + + for (auto muttype_iter : mutation_types_) + muttype_iter.second->mutation_block_ = nullptr; + + for (Chromosome *chromosome : chromosomes_) + chromosome->mutation_block_ = nullptr; + + population_.mutation_block_ = nullptr; + } } void Species::_MakeHaplosomeMetadataRecords(void) @@ -1288,6 +1303,7 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos #elif STD_UNORDERED_MAP_HASHING std::unordered_map mutations; #endif + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; while (!infile.eof()) { @@ -1367,9 +1383,9 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromTextFile): mutation type m"<< mutation_type_id << " is not nucleotide-based, but a nucleotide value for a mutation of this type was supplied." << EidosTerminate(); // construct the new mutation; NOTE THAT THE STACKING POLICY IS NOT CHECKED HERE, AS THIS IS NOT CONSIDERED THE ADDITION OF A MUTATION! - MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); + MutationIndex new_mut_index = mutation_block_->NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, dominance_coeff, subpop_index, tick, nucleotide); + Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, dominance_coeff, subpop_index, tick, nucleotide); // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry mutations.emplace(polymorphism_id, new_mut_index); @@ -1391,7 +1407,6 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos population_.InvalidateMutationReferencesCache(); // Now we are in the Haplosomes section, which should take us to the end of the chromosome unless there is an Ancestral Sequence section - Mutation *mut_block_ptr = gSLiM_Mutation_Block; #ifndef _OPENMP MutationRunContext &mutrun_context = chromosome->ChromosomeMutationRunContextForThread(omp_get_thread_num()); // when not parallel, we have only one MutationRunContext #endif @@ -2038,6 +2053,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid // Mutations section std::unique_ptr raii_mutations(new MutationIndex[mutation_map_size]); MutationIndex *mutations = raii_mutations.get(); + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; if (!mutations) EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): could not allocate mutations buffer." << EidosTerminate(); @@ -2125,9 +2141,9 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): mutation type m" << mutation_type_id << " is not nucleotide-based, but a nucleotide value for a mutation of this type was supplied." << EidosTerminate(); // construct the new mutation; NOTE THAT THE STACKING POLICY IS NOT CHECKED HERE, AS THIS IS NOT CONSIDERED THE ADDITION OF A MUTATION! - MutationIndex new_mut_index = SLiM_NewMutationFromBlock(); + MutationIndex new_mut_index = mutation_block_->NewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, dominance_coeff, subpop_index, tick, nucleotide); + Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, dominance_coeff, subpop_index, tick, nucleotide); // read the tag value, if present if (has_object_tags) @@ -2167,7 +2183,6 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid } // Haplosomes section - Mutation *mut_block_ptr = gSLiM_Mutation_Block; bool use_16_bit = (mutation_map_size <= UINT16_MAX - 1); // 0xFFFF is reserved as the start of our various tags std::unique_ptr raii_haplosomebuf(new MutationIndex[mutation_map_size]); // allowing us to use emplace_back_bulk() for speed MutationIndex *haplosomebuf = raii_haplosomebuf.get(); @@ -2712,6 +2727,10 @@ void Species::RunInitializeCallbacks(void) CheckMutationStackPolicy(); + // Except in no-genetics species, make a MutationBlock object to keep our mutations in + if (has_genetics_) + CreateAndPromulgateMutationBlock(); + // In nucleotide-based models, process the mutationMatrix parameters for genomic element types to calculate the maximum mutation rate if (nucleotide_based_) CacheNucleotideMatrices(); @@ -2795,6 +2814,29 @@ void Species::RunInitializeCallbacks(void) AllocateTreeSequenceTables(); } +void Species::CreateAndPromulgateMutationBlock(void) +{ + if (mutation_block_) + EIDOS_TERMINATION << "ERROR (Species::CreateAndPromulgateMutationBlock): (internal error) a mutation block has already been allocated." << EidosTerminate(); + + // first we make a new MutationBlock object for ourselves + mutation_block_ = new MutationBlock(*this, (int)TraitCount()); + + // then we promulgate it to the masses, so that they have it on hand (avoiding the non-local memory access + // of getting it from us), since it is referred to very actively in many places + + // give it to all MutationType objects in this species + for (auto muttype_iter : mutation_types_) + muttype_iter.second->mutation_block_ = mutation_block_; + + // give it to all Chromosome objects in this species + for (Chromosome *chromosome : chromosomes_) + chromosome->mutation_block_ = mutation_block_; + + // give it to the Population object in this species + population_.mutation_block_ = mutation_block_; +} + void Species::EndCurrentChromosome(bool starting_new_chromosome) { // Check for complete/correct initialization of the currently initializing chromosome. The error messages emitted are tailored @@ -9743,6 +9785,8 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapmutation_buffer_; + for (auto mut_info_iter : p_mutInfoMap) { slim_mutationid_t mutation_id = mut_info_iter.first; @@ -9789,10 +9833,10 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapNewMutationFromBlock(); // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); // FIXME MULTITRAIT + Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); // FIXME MULTITRAIT // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry p_mutIndexMap[mutation_id] = new_mut_index; diff --git a/core/species.h b/core/species.h index 5eccef10..3aeeb0b2 100644 --- a/core/species.h +++ b/core/species.h @@ -55,6 +55,7 @@ extern "C" { class Community; class EidosInterpreter; class Individual; +class MutationBlock; class MutationType; class GenomicElementType; class InteractionType; @@ -181,6 +182,11 @@ class Species : public EidosDictionaryUnretained bool has_genetics_ = true; // false if the species has no mutation, no recombination, no muttypes/getypes, no genomic elements + // We keep a MutationBlock object that stores all of the Mutation objects that belong to this species. + // Our mutations get allocated and freed using this block, and we use MutationIndex to reference them. + // This remains nullptr in no-genetics species, and is allocated only after initialize() is done. + MutationBlock *mutation_block_ = nullptr; // OWNED; contains all of our mutations + // for multiple chromosomes, we now have a vector of pointers to Chromosome objects, // as well as hash tables for quick lookup by id and symbol #if EIDOS_ROBIN_HOOD_HASHING @@ -230,9 +236,9 @@ class Species : public EidosDictionaryUnretained // private initialization methods #if EIDOS_ROBIN_HOOD_HASHING typedef robin_hood::unordered_flat_map SUBPOP_REMAP_HASH; - #elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING typedef std::unordered_map SUBPOP_REMAP_HASH; - #endif +#endif SLiMFileFormat FormatOfPopulationFile(const std::string &p_file_string); // determine the format of a file/folder at the given path using leading bytes, etc. void _CleanAllReferencesToSpecies(EidosInterpreter *p_interpreter); // clean up in anticipation of loading new species state @@ -322,17 +328,17 @@ class Species : public EidosDictionaryUnretained bool tables_initialized_ = false; // not checked everywhere, just when allocing and freeing, to avoid crashes - std::vector remembered_nodes_; // used to be called remembered_genomes_, but it remembers tskit nodes, which might + std::vector remembered_nodes_; // used to be called remembered_genomes_, but it remembers tskit nodes, which might // actually be shared by multiple haplosomes in different chromosomes //Individual *current_new_individual_; #if EIDOS_ROBIN_HOOD_HASHING typedef robin_hood::unordered_flat_map INDIVIDUALS_HASH; - #elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING typedef std::unordered_map INDIVIDUALS_HASH; - #endif +#endif INDIVIDUALS_HASH tabled_individuals_hash_; // look up individuals table row numbers from pedigree IDs - + bool running_coalescence_checks_ = false; // true if we check for coalescence after each simplification bool running_treeseq_crosschecks_ = false; // true if crosschecks between our tree sequence tables and SLiM's data are enabled int treeseq_crosschecks_interval_ = 1; // crosschecks, if enabled, will be done every treeseq_crosschecks_interval_ cycles @@ -468,6 +474,7 @@ class Species : public EidosDictionaryUnretained // Running cycles std::vector CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, int64_t p_chromosome_id); void RunInitializeCallbacks(void); + void CreateAndPromulgateMutationBlock(void); void EndCurrentChromosome(bool starting_new_chromosome); bool HasDoneAnyInitialization(void); void PrepareForCycle(void); @@ -524,6 +531,7 @@ class Species : public EidosDictionaryUnretained inline __attribute__((always_inline)) slim_tick_t TickPhase(void) { return tick_phase_; } inline __attribute__((always_inline)) bool HasGenetics(void) { return has_genetics_; } + inline __attribute__((always_inline)) MutationBlock *SpeciesMutationBlock(void) { return mutation_block_; } // FIXME MULTICHROM: We could cache a ref to this in some key spots like Chromosome, MutationType, and Subpopulation inline __attribute__((always_inline)) const std::map &MutationTypes(void) const { return mutation_types_; } inline __attribute__((always_inline)) const std::map &GenomicElementTypes(void) { return genomic_element_types_; } inline __attribute__((always_inline)) size_t GraveyardSize(void) const { return graveyard_.size(); } diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index b43defd5..3b1e3aee 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -27,6 +27,7 @@ #include "subpopulation.h" #include "polymorphism.h" #include "interaction_type.h" +#include "mutation_block.h" #include "log_file.h" #include @@ -2050,7 +2051,7 @@ EidosValue_SP Species::GetProperty(EidosGlobalStringID p_property_id) } case gID_mutations: { - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; int registry_size; const MutationIndex *registry = population_.MutationRegistry(®istry_size); EidosValue_Object *vec = (new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Mutation_Class))->resize_no_initialize_RR(registry_size); @@ -3247,7 +3248,7 @@ EidosValue_SP Species::ExecuteMethod_mutationsOfType(EidosGlobalStringID p_metho EidosValue *mutType_value = p_arguments[0].get(); MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &community_, this, "mutationsOfType()"); // SPECIES CONSISTENCY CHECK - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES // track calls per cycle to Species::ExecuteMethod_mutationsOfType() and Species::ExecuteMethod_countOfMutationsOfType() @@ -3343,7 +3344,7 @@ EidosValue_SP Species::ExecuteMethod_countOfMutationsOfType(EidosGlobalStringID EidosValue *mutType_value = p_arguments[0].get(); MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &community_, this, "countOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES // track calls per cycle to Species::ExecuteMethod_mutationsOfType() and Species::ExecuteMethod_countOfMutationsOfType() @@ -3632,7 +3633,7 @@ EidosValue_SP Species::ExecuteMethod_outputMutations(EidosGlobalStringID p_metho std::ostream &out = *(has_file ? (std::ostream *)&outfile : (std::ostream *)&output_stream); int mutations_count = mutations_value->Count(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; if (mutations_count > 0) { @@ -4192,7 +4193,7 @@ EidosValue_SP Species::ExecuteMethod_subsetMutations(EidosGlobalStringID p_metho // We will scan forward looking for a match, and will keep track of the first match we find. If we only find one, we return // a singleton; if we find a second, we will start accumulating a vector result. - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; int registry_size; const MutationIndex *registry = population_.MutationRegistry(®istry_size); int match_count = 0, registry_index; diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 8158324d..a406cc19 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -24,6 +24,7 @@ #include "slim_globals.h" #include "population.h" #include "interaction_type.h" +#include "mutation_block.h" #include "eidos_call_signature.h" #include "eidos_property_signature.h" #include "eidos_ast_node.h" @@ -423,7 +424,7 @@ void Subpopulation::CheckIndividualIntegrity(void) const std::vector &chromosomes = species_.Chromosomes(); size_t chromosomes_count = chromosomes.size(); bool has_genetics = species_.HasGenetics(); - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = has_genetics ? species_.SpeciesMutationBlock()->mutation_buffer_ : nullptr; if (!has_genetics && (chromosomes_count != 0)) EIDOS_TERMINATION << "ERROR (Community::Species_CheckIntegrity): (internal error) chromosome present in no-genetics species." << EidosTerminate(); @@ -2455,7 +2456,8 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int SLIM_PROFILE_BLOCK_START(); #endif - slim_objectid_t mutation_type_id = (gSLiM_Mutation_Block + p_mutation)->mutation_type_ptr_->mutation_type_id_; + Mutation *mut_block_ptr = species_.SpeciesMutationBlock()->mutation_buffer_; + slim_objectid_t mutation_type_id = (mut_block_ptr + p_mutation)->mutation_type_ptr_->mutation_type_id_; for (SLiMEidosBlock *mutationEffect_callback : p_mutationEffect_callbacks) { @@ -2538,7 +2540,7 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int else { // local variables for the callback parameters that we might need to allocate here, and thus need to free below - EidosValue_Object local_mut(gSLiM_Mutation_Block + p_mutation, gSLiM_Mutation_Class); + EidosValue_Object local_mut(mut_block_ptr + p_mutation, gSLiM_Mutation_Class); EidosValue_Float local_effect(p_computed_fitness); // We need to actually execute the script; we start a block here to manage the lifetime of the symbol table @@ -2957,7 +2959,7 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom single_callback_mut_type = species_.MutationTypeWithID(mutation_type_id); } - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species_.SpeciesMutationBlock()->mutation_buffer_; if (haplosome1_null && haplosome2_null) { @@ -2979,7 +2981,7 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome_iter, *haplosome_max; - mutrun->beginend_nonneutral_pointers(&haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); + mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); @@ -3023,8 +3025,8 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; - mutrun1->beginend_nonneutral_pointers(&haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); - mutrun2->beginend_nonneutral_pointers(&haplosome2_iter, &haplosome2_max, nonneutral_change_counter, nonneutral_regime); + mutrun1->beginend_nonneutral_pointers(mut_block_ptr, &haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); + mutrun2->beginend_nonneutral_pointers(mut_block_ptr, &haplosome2_iter, &haplosome2_max, nonneutral_change_counter, nonneutral_regime); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); @@ -3284,7 +3286,7 @@ double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vect single_callback_mut_type = species_.MutationTypeWithID(mutation_type_id); } - Mutation *mut_block_ptr = gSLiM_Mutation_Block; + Mutation *mut_block_ptr = species_.SpeciesMutationBlock()->mutation_buffer_; const int32_t mutrun_count = haplosome->mutrun_count_; double w = 1.0; @@ -3296,7 +3298,7 @@ double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vect // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome_iter, *haplosome_max; - mutrun->beginend_nonneutral_pointers(&haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); + mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); diff --git a/core/trait.h b/core/trait.h index 5d033a9a..6b37b0d5 100644 --- a/core/trait.h +++ b/core/trait.h @@ -86,6 +86,7 @@ class Trait : public EidosDictionaryRetained inline __attribute__((always_inline)) int64_t Index(void) const { return index_; } inline __attribute__((always_inline)) void SetIndex(int64_t p_index) { index_ = p_index; } // only from AddTrait() + inline __attribute__((always_inline)) TraitType Type(void) const { return type_; } inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetFixed_ and individualOffsetFixedValue_ diff --git a/eidos/eidos_value.cpp b/eidos/eidos_value.cpp index 187c0341..7b32d571 100644 --- a/eidos/eidos_value.cpp +++ b/eidos/eidos_value.cpp @@ -1739,7 +1739,7 @@ void EidosValue_Float::erase_index(size_t p_index) #pragma mark - // See comments on EidosValue_Object::EidosValue_Object() below. Note this is shared by all species. -std::vector gEidosValue_Object_Mutation_Registry; +std::vector EidosValue_Object::static_EidosValue_Object_Mutation_Registry; EidosValue_Object::EidosValue_Object(const EidosClass *p_class) : EidosValue(EidosValueType::kValueObject), values_(&singleton_value_), count_(0), capacity_(1), class_(p_class) @@ -1755,17 +1755,18 @@ EidosValue_Object::EidosValue_Object(const EidosClass *p_class) : EidosValue(Eid // is some way to do this without pushing the hack down into Eidos, but at the moment I'm not seeing it. // On the bright side, this scheme actually seems pretty robust; the only way it fails is if somebody avoids // using the constructor or the destructor for EidosValue_Object, I think, which seems unlikely. - // Note this is shared by all species, since the mutation block itself is shared by all species. + // Note this is shared by all species, since the mutation block itself is shared by all species; and in + // SLiMgui is is shared across all of the running simulations, but it turns out that works fine. const std::string *element_type = &(class_->ClassName()); if (element_type == &gEidosStr_Mutation) { - THREAD_SAFETY_IN_ACTIVE_PARALLEL("EidosValue_Object::EidosValue_Object(): gEidosValue_Object_Mutation_Registry change"); + THREAD_SAFETY_IN_ACTIVE_PARALLEL("EidosValue_Object::EidosValue_Object(): static_EidosValue_Object_Mutation_Registry change"); - gEidosValue_Object_Mutation_Registry.emplace_back(this); + static_EidosValue_Object_Mutation_Registry.emplace_back(this); registered_for_patching_ = true; - //std::cout << "pushed Mutation EidosValue_Object, count == " << gEidosValue_Object_Mutation_Registry.size() << std::endl; + //std::cout << "pushed Mutation EidosValue_Object, count == " << static_EidosValue_Object_Mutation_Registry.size() << std::endl; } else { @@ -1865,16 +1866,16 @@ EidosValue_Object::~EidosValue_Object(void) // See comment on EidosValue_Object::EidosValue_Object() above if (registered_for_patching_) { - THREAD_SAFETY_IN_ACTIVE_PARALLEL("EidosValue_Object::~EidosValue_Object(): gEidosValue_Object_Mutation_Registry change"); + THREAD_SAFETY_IN_ACTIVE_PARALLEL("EidosValue_Object::~EidosValue_Object(): static_EidosValue_Object_Mutation_Registry change"); - auto erase_iter = std::find(gEidosValue_Object_Mutation_Registry.begin(), gEidosValue_Object_Mutation_Registry.end(), this); + auto erase_iter = std::find(static_EidosValue_Object_Mutation_Registry.begin(), static_EidosValue_Object_Mutation_Registry.end(), this); - if (erase_iter != gEidosValue_Object_Mutation_Registry.end()) - gEidosValue_Object_Mutation_Registry.erase(erase_iter); + if (erase_iter != static_EidosValue_Object_Mutation_Registry.end()) + static_EidosValue_Object_Mutation_Registry.erase(erase_iter); else EIDOS_TERMINATION << "ERROR (EidosValue_Object::~EidosValue_Object): (internal error) unregistered EidosValue_Object of class Mutation." << EidosTerminate(nullptr); - //std::cout << "popped Mutation EidosValue_Object, count == " << gEidosValue_Object_Mutation_Registry.size() << std::endl; + //std::cout << "popped Mutation EidosValue_Object, count == " << static_EidosValue_Object_Mutation_Registry.size() << std::endl; } if (class_uses_retain_release_) @@ -1892,30 +1893,6 @@ EidosValue_Object::~EidosValue_Object(void) free(values_); } -// Provided to SLiM for the Mutation-pointer hack; see EidosValue_Object::EidosValue_Object() for comments -void EidosValue_Object::PatchPointersByAdding(std::uintptr_t p_pointer_difference) -{ - for (size_t i = 0; i < count_; ++i) - { - std::uintptr_t old_element_ptr = reinterpret_cast(values_[i]); - std::uintptr_t new_element_ptr = old_element_ptr + p_pointer_difference; - - values_[i] = reinterpret_cast(new_element_ptr); // NOLINT(*-no-int-to-ptr) - } -} - -// Provided to SLiM for the Mutation-pointer hack; see EidosValue_Object::EidosValue_Object() for comments -void EidosValue_Object::PatchPointersBySubtracting(std::uintptr_t p_pointer_difference) -{ - for (size_t i = 0; i < count_; ++i) - { - std::uintptr_t old_element_ptr = reinterpret_cast(values_[i]); - std::uintptr_t new_element_ptr = old_element_ptr - p_pointer_difference; - - values_[i] = reinterpret_cast(new_element_ptr); // NOLINT(*-no-int-to-ptr) - } -} - void EidosValue_Object::RaiseForClassMismatch(void) const { EIDOS_TERMINATION << "ERROR (EidosValue_Object::RaiseForClassMismatch): the type of an object cannot be changed." << EidosTerminate(nullptr); diff --git a/eidos/eidos_value.h b/eidos/eidos_value.h index fad37eae..5897fcfb 100644 --- a/eidos/eidos_value.h +++ b/eidos/eidos_value.h @@ -951,7 +951,7 @@ class EidosValue_Object final : public EidosValue { private: typedef EidosValue super; - + protected: // singleton/vector design: values_ will either point to singleton_value_, or to a malloced buffer; it will never be nullptr // in the case of a zero-length vector, note that values_ will point to singleton_value_ with count_ == 0 but capacity_ == 1 @@ -987,10 +987,6 @@ class EidosValue_Object final : public EidosValue } void RaiseForClassMismatch(void) const; - // Provided to SLiM for the Mutation-pointer hack; see EidosValue_Object::EidosValue_Object() for comments - void PatchPointersByAdding(std::uintptr_t p_pointer_difference); - void PatchPointersBySubtracting(std::uintptr_t p_pointer_difference); - public: EidosValue_Object(void) = delete; // no default constructor EidosValue_Object& operator=(const EidosValue_Object&) = delete; // no copying @@ -1083,7 +1079,7 @@ class EidosValue_Object final : public EidosValue else reserve(capacity_ << 1); } - + void erase_index(size_t p_index); // a weak substitute for erase() inline __attribute__((always_inline)) EidosObject **data_mutable(void) { WILL_MODIFY(this); return values_; } // the accessors below should be used to modify, since they handle Retain()/Release() @@ -1108,7 +1104,12 @@ class EidosValue_Object final : public EidosValue void set_object_element_no_check_no_previous_RR(EidosObject *p_object, size_t p_index); // specifies retain/release, previous value assumed invalid from resize_no_initialize_RR void set_object_element_no_check_NORR(EidosObject *p_object, size_t p_index); // specifies no retain/release - friend void SLiM_IncreaseMutationBlockCapacity(void); // for PatchPointersByAdding() / PatchPointersBySubtracting() +private: + // See comments on EidosValue_Object::EidosValue_Object(). Note this is shared by all species, and in + // SLiMgui it is shared by all running simulations, so we need to be careful not to step on any toes. + static std::vector static_EidosValue_Object_Mutation_Registry; + + friend class MutationBlock; // so it can access the above registry; see IncreaseMutationBlockCapacity() }; inline __attribute__((always_inline)) void EidosValue_Object::push_object_element_CRR(EidosObject *p_object) From 2de00ef31e98eef92ece10491b54d44a93dea469 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 16 Oct 2025 15:26:14 -0400 Subject: [PATCH 017/107] fix bug in Individual trait property access --- core/individual.cpp | 50 +++++++++++++++++++++++++++++-------- core/slim_test_genetics.cpp | 2 ++ core/species_eidos.cpp | 8 ++++-- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/core/individual.cpp b/core/individual.cpp index 8f8d7017..53dc3fa9 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -2627,7 +2627,7 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue Species &species = subpopulation_->species_; Trait *trait = species.TraitFromStringID(p_property_id); - if (trait) + if (trait) // ACCELERATED { trait_info_[trait->Index()].value_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); return; @@ -3039,7 +3039,7 @@ void Individual::SetProperty_Accelerated_age(EidosGlobalStringID p_property_id, void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) { -#pragma unused (p_property_id, p_source_size) +#pragma unused (p_property_id) const Individual **individuals_buffer = (const Individual **)p_values; Species *species = Community::SpeciesForIndividualsVector(individuals_buffer, (int)p_values_size); const double *source_data = p_source.FloatData(); @@ -3049,23 +3049,53 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope Trait *trait = species->TraitFromStringID(p_property_id); int64_t trait_index = trait->Index(); - for (size_t value_index = 0; value_index < p_values_size; ++value_index) + if (p_source_size == 1) { - const Individual *value = individuals_buffer[value_index]; + slim_effect_t source_value = (slim_effect_t)source_data[0]; - value->trait_info_[trait_index].value_ = (slim_effect_t)source_data[value_index]; + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + + value->trait_info_[trait_index].value_ = source_value; + } + } + else + { + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + + value->trait_info_[trait_index].value_ = (slim_effect_t)source_data[value_index]; + } } } else { // with a mixed-species target, the species and trait have to be looked up for each individual - for (size_t value_index = 0; value_index < p_values_size; ++value_index) + if (p_source_size == 1) { - const Individual *value = individuals_buffer[value_index]; - Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); - int64_t trait_index = trait->Index(); + slim_effect_t source_value = (slim_effect_t)source_data[0]; - value->trait_info_[trait_index].value_ = (slim_effect_t)source_data[value_index]; + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); + int64_t trait_index = trait->Index(); + + value->trait_info_[trait_index].value_ = source_value; + } + } + else + { + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); + int64_t trait_index = trait->Index(); + + value->trait_info_[trait_index].value_ = (slim_effect_t)source_data[value_index]; + } } } } diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 8aace2da..40ee2963 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1085,6 +1085,8 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.weight, rep(0.0, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0; if (!identical(p1.individuals.height, rep(10.0, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 10.0; if (!identical(p1.individuals.weight, rep(10.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0:14; if (!identical(p1.individuals.height, 10.0:14)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 11.0:15; if (!identical(p1.individuals.weight, 11.0:15)) stop(); }"); } std::cout << "_RunMultitraitTests() done" << std::endl; diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 3b1e3aee..54f4ca57 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1714,7 +1714,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string else { // see also SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this - EidosPropertySignature_CSP signature((new EidosPropertySignature(name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP signature((new EidosPropertySignature(name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class)) + ->MarkAsDynamicWithOwner("Trait")); gSLiM_Species_Class->AddSignatureForProperty(signature); } @@ -1734,7 +1735,10 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string else { // see also SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this - EidosPropertySignature_CSP signature((new EidosPropertySignature(name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")->DeclareAcceleratedGet(Individual::GetProperty_Accelerated_TRAIT_VALUE)); + EidosPropertySignature_CSP signature((new EidosPropertySignature(name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + MarkAsDynamicWithOwner("Trait")-> + DeclareAcceleratedGet(Individual::GetProperty_Accelerated_TRAIT_VALUE)-> + DeclareAcceleratedSet(Individual::SetProperty_Accelerated_TRAIT_VALUE)); gSLiM_Individual_Class->AddSignatureForProperty(signature); } From dbd38a808b90fd4666e9c1bee7fabb79b94558b5 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 16 Oct 2025 16:56:36 -0400 Subject: [PATCH 018/107] fix Trait and MutationBlock leaks --- core/individual.cpp | 36 +++++++++++++++++++++++++++++++++--- core/mutation_block.cpp | 17 +++++++++++++++++ core/mutation_block.h | 1 + core/species.cpp | 5 +++++ core/subpopulation.cpp | 7 +++++++ core/subpopulation.h | 1 + 6 files changed, 64 insertions(+), 3 deletions(-) diff --git a/core/individual.cpp b/core/individual.cpp index 53dc3fa9..ee5e6c2c 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -85,6 +85,7 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu } // Set up per-trait information such as phenotype caches and individual offsets + trait_info_ = nullptr; _InitializePerTraitInformation(); // Initialize tag values to the "unset" value @@ -106,8 +107,8 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu void Individual::_InitializePerTraitInformation(void) { - // Set up per-trait individual-level information such as individual offsets. This is called by - // Individual::Individual(), but also in various other places where individuals are re-used. + // Set up per-trait individual-level information such as individual offsets. This is called by Individual::Individual(), + // but also in various other places where individuals are re-used, so the trait_info_ record might already be allocated. // FIXME MULTITRAIT: this will probably be a pain point; maybe we can skip it if offsets have never been changed by the user? // I imagine a design where there is a bool flag that says "the offsets for this individual have been initialized". This @@ -134,17 +135,46 @@ void Individual::_InitializePerTraitInformation(void) if (trait_count == 1) { +#if DEBUG + // If there is existing trait info, the number of traits should not have changed, so we should not need to adjust + if (trait_info_ && (trait_info_ != &trait_info_0_)) + { + free(trait_info_); + std::cout << "_InitializePerTraitInformation(): (internal error) unmatched trait info! (case 1)" << std::endl; + } +#endif + trait_info_ = &trait_info_0_; trait_info_0_.value_ = 0.0; trait_info_0_.offset_ = traits[0]->DrawIndividualOffset(); } else if (trait_count == 0) { +#if DEBUG + // If there is existing trait info, the number of traits should not have changed, so we should not need to adjust + if (trait_info_) + { + if (trait_info_ != &trait_info_0_) + free(trait_info_); + std::cout << "_InitializePerTraitInformation(): (internal error) unmatched trait info! (case 2)" << std::endl; + } +#endif + trait_info_ = nullptr; } else { - trait_info_ = static_cast(malloc(trait_count * sizeof(SLiM_PerTraitInfo))); +#if DEBUG + // If there is existing trait info, the number of traits should not have changed, so we should not need to adjust + // Note that in this case if there is allocated trait info we assume it is the correct size; we have no way to check that + if (trait_info_ && (trait_info_ == &trait_info_0_)) + { + std::cout << "_InitializePerTraitInformation(): (internal error) unmatched trait info! (case 3)" << std::endl; + } +#endif + + if (!trait_info_) + trait_info_ = static_cast(malloc(trait_count * sizeof(SLiM_PerTraitInfo))); for (int trait_index = 0; trait_index < trait_count; ++trait_index) { diff --git a/core/mutation_block.cpp b/core/mutation_block.cpp index cc238699..81d8e8f9 100644 --- a/core/mutation_block.cpp +++ b/core/mutation_block.cpp @@ -50,6 +50,23 @@ MutationBlock::MutationBlock(Species &p_species, int p_trait_count) : species_(p free_index_ = 0; } +MutationBlock::~MutationBlock(void) +{ + free(mutation_buffer_); + mutation_buffer_ = nullptr; + + free(refcount_buffer_); + refcount_buffer_ = nullptr; + + free(trait_info_buffer_); + trait_info_buffer_ = nullptr; + + capacity_ = 0; + free_index_ = -1; + last_used_index_ = -1; + trait_count_ = 0; +} + void MutationBlock::IncreaseMutationBlockCapacity(void) { // We do not use a THREAD_SAFETY macro here because this needs to be checked in release builds also; diff --git a/core/mutation_block.h b/core/mutation_block.h index 4f1396a6..596c6853 100644 --- a/core/mutation_block.h +++ b/core/mutation_block.h @@ -62,6 +62,7 @@ class MutationBlock #endif explicit MutationBlock(Species &p_species, int p_trait_count); + ~MutationBlock(void); void IncreaseMutationBlockCapacity(void); void ZeroRefcountBlock(MutationRun &p_mutation_registry); diff --git a/core/species.cpp b/core/species.cpp index ac5d6e7b..631c385b 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -187,6 +187,11 @@ Species::~Species(void) std::fill(chromosome_for_haplosome_index_.begin(), chromosome_for_haplosome_index_.end(), nullptr); chromosome_for_haplosome_index_.clear(); + // Free our Trait objects + for (Trait *trait : traits_) + delete trait; + traits_.clear(); + // Free our MutationBlock, and make those with copies of it forget it; see CreateAndPromulgateMutationBlock { delete mutation_block_; diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index a406cc19..2f6457f9 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -4503,6 +4503,7 @@ bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigree individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent1); // Draw new individual trait offsets from each trait's individual-offset distribution + // Note that we reuse the existing trait_info_ buffer, with the same number of traits individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one @@ -4891,6 +4892,7 @@ bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pe individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent1); // Draw new individual trait offsets from each trait's individual-offset distribution + // Note that we reuse the existing trait_info_ buffer, with the same number of traits individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one @@ -4977,6 +4979,7 @@ bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pe individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent1); // Draw new individual trait offsets from each trait's individual-offset distribution + // Note that we reuse the existing trait_info_ buffer, with the same number of traits individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one @@ -5060,6 +5063,7 @@ bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreei individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); // Draw new individual trait offsets from each trait's individual-offset distribution + // Note that we reuse the existing trait_info_ buffer, with the same number of traits individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one @@ -5262,6 +5266,7 @@ bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreei individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); // Draw new individual trait offsets from each trait's individual-offset distribution + // Note that we reuse the existing trait_info_ buffer, with the same number of traits individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one @@ -5538,6 +5543,7 @@ bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_ped individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); // Draw new individual trait offsets from each trait's individual-offset distribution + // Note that we reuse the existing trait_info_ buffer, with the same number of traits individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one @@ -5624,6 +5630,7 @@ bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_ped individual->InheritSpatialPosition(species_.SpatialDimensionality(), p_parent); // Draw new individual trait offsets from each trait's individual-offset distribution + // Note that we reuse the existing trait_info_ buffer, with the same number of traits individual->_InitializePerTraitInformation(); // Configure the offspring's haplosomes one by one diff --git a/core/subpopulation.h b/core/subpopulation.h index 7ca8c2a6..4d8e621a 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -294,6 +294,7 @@ class Subpopulation : public EidosDictionaryUnretained back->subpopulation_ = this; // Draw new individual trait offsets from each trait's individual-offset distribution + // Note that we reuse the existing trait_info_ buffer, with the same number of traits back->_InitializePerTraitInformation(); return back; From 167ee4a6e59a6248e8ecc7768e5b5b2bafdb0998 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 17 Oct 2025 20:36:00 -0400 Subject: [PATCH 019/107] add per-trait effect size and dominance to Substitution --- VERSIONS | 1 + core/mutation.cpp | 4 ++-- core/mutation.h | 2 +- core/mutation_type.cpp | 4 ++-- core/species.cpp | 4 ++-- core/species.h | 2 +- core/substitution.cpp | 28 ++++++++++++++++++++++++++++ core/substitution.h | 19 +++++++++++++++++-- 8 files changed, 54 insertions(+), 10 deletions(-) diff --git a/VERSIONS b/VERSIONS index cb6fc2d1..3acc5ece 100644 --- a/VERSIONS +++ b/VERSIONS @@ -59,6 +59,7 @@ multitrait branch: make code completion work for the new dynamic properties on Species and Individual generated by initializeTrait() shift from a single global mutation block into per-species mutation blocks, and make a new C++ class, MutationBlock, to encapsulate this this is a forced move because we want the mutation block to have a separate buffer of per-trait state for mutations, and the number of traits varies among species + add effect size and dominance coefficient properties to Mutation and Substitution (but not hooked up to the simulation yet) version 5.1 (Eidos version 4.1): diff --git a/core/mutation.cpp b/core/mutation.cpp index 637fadb3..1c7322af 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -70,7 +70,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ Trait *trait = species.Traits()[trait_index]; TraitType traitType = trait->Type(); - traitInfoRec->mutation_effect_ = selection_coeff_; + traitInfoRec->effect_size_ = selection_coeff_; traitInfoRec->dominance_coeff_ = dominance_coeff_; if (traitType == TraitType::kMultiplicative) @@ -168,7 +168,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ Trait *trait = species.Traits()[trait_index]; TraitType traitType = trait->Type(); - traitInfoRec->mutation_effect_ = selection_coeff_; + traitInfoRec->effect_size_ = selection_coeff_; traitInfoRec->dominance_coeff_ = dominance_coeff_; if (traitType == TraitType::kMultiplicative) diff --git a/core/mutation.h b/core/mutation.h index 881ab7a1..581608d3 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -59,7 +59,7 @@ typedef int32_t MutationIndex; // with a number of records per mutation that is determined when it is constructed. typedef struct _MutationTraitInfo { - slim_effect_t mutation_effect_; // selection coefficient (s) or additive effect (a) + slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default // We cache values used in the fitness calculation code, for speed. These are the final fitness effects of this mutation diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 41b1154d..061af4d1 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -87,9 +87,9 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr all_pure_neutral_DFE_ = ((p_dfe_type == DFEType::kFixed) && (p_dfe_parameters[0] == 0.0)); // set up DE entries for all traits - int64_t trait_count = species_.TraitCount(); + int trait_count = species_.TraitCount(); - for (int64_t trait_index = 0; trait_index < trait_count; trait_index++) + for (int trait_index = 0; trait_index < trait_count; trait_index++) { EffectDistributionInfo ed_info; diff --git a/core/species.cpp b/core/species.cpp index 631c385b..070b87d8 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -609,7 +609,7 @@ void Species::GetTraitIndicesFromEidosValue(std::vector &trait_indices, { EidosValueType traits_value_type = traits_value->Type(); int traits_value_count = traits_value->Count(); - int64_t trait_count = TraitCount(); + int trait_count = TraitCount(); switch (traits_value_type) { @@ -2825,7 +2825,7 @@ void Species::CreateAndPromulgateMutationBlock(void) EIDOS_TERMINATION << "ERROR (Species::CreateAndPromulgateMutationBlock): (internal error) a mutation block has already been allocated." << EidosTerminate(); // first we make a new MutationBlock object for ourselves - mutation_block_ = new MutationBlock(*this, (int)TraitCount()); + mutation_block_ = new MutationBlock(*this, TraitCount()); // then we promulgate it to the masses, so that they have it on hand (avoiding the non-local memory access // of getting it from us), since it is referred to very actively in many places diff --git a/core/species.h b/core/species.h index 3aeeb0b2..3281b306 100644 --- a/core/species.h +++ b/core/species.h @@ -448,7 +448,7 @@ class Species : public EidosDictionaryUnretained // Trait configuration and access inline __attribute__((always_inline)) const std::vector &Traits(void) { return traits_; } - inline __attribute__((always_inline)) int64_t TraitCount(void) { return (int64_t)traits_.size(); } + inline __attribute__((always_inline)) int TraitCount(void) { return (int)traits_.size(); } Trait *TraitFromName(const std::string &p_name) const; inline __attribute__((always_inline)) Trait *TraitFromStringID(EidosGlobalStringID p_string_id) const { diff --git a/core/substitution.cpp b/core/substitution.cpp index 50789c16..8b1b8cca 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -23,6 +23,7 @@ #include "eidos_call_signature.h" #include "eidos_property_signature.h" #include "species.h" +#include "mutation_block.h" #include #include @@ -40,11 +41,38 @@ Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : { AddKeysAndValuesFrom(&p_mutation); // No call to ContentsChanged() here; we know we use Dictionary not DataFrame, and Mutation already vetted the dictionary + + // Copy per-trait information over from the mutation object + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationIndex mut_index = mutation_block->IndexInBlock(&p_mutation); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + int trait_count = species.TraitCount(); + + trait_info_ = (SubstitutionTraitInfo *)malloc(trait_count * sizeof(SubstitutionTraitInfo)); + + for (int trait_index = 0; trait_index < trait_count; trait_index++) + { + trait_info_[trait_index].effect_size_ = mut_trait_info[trait_index].effect_size_; + trait_info_[trait_index].dominance_coeff_ = mut_trait_info[trait_index].dominance_coeff_; + } } Substitution::Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide) : mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(static_cast(p_selection_coeff)), dominance_coeff_(static_cast(p_dominance_coeff)), subpop_index_(p_subpop_index), origin_tick_(p_tick), fixation_tick_(p_fixation_tick), chromosome_index_(p_chromosome_index), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id), tag_value_(SLIM_TAG_UNSET_VALUE) { + // FIXME MULTITRAIT: This code path is hit when loading substitutions from an output file, also needs to initialize the multitrait info; this is just a + // placeholder. The file being read in ought to specify per-trait values, which hasn't happened yet, so there are lots of details to be worked out... + Species &species = mutation_type_ptr_->species_; + int trait_count = species.TraitCount(); + + trait_info_ = (SubstitutionTraitInfo *)malloc(trait_count * sizeof(SubstitutionTraitInfo)); + + for (int trait_index = 0; trait_index < trait_count; trait_index++) + { + trait_info_[trait_index].effect_size_ = 0.0; + trait_info_[trait_index].dominance_coeff_ = 0.0; + } } void Substitution::PrintForSLiMOutput(std::ostream &p_out) const diff --git a/core/substitution.h b/core/substitution.h index 0791cbb8..162f02da 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -37,6 +37,18 @@ extern EidosClass *gSLiM_Substitution_Class; +// This structure contains all of the information about how a substitution influenced a particular trait: in particular, its +// effect size and dominance coefficient. Each substitution keeps this information for each trait in its species, and since +// the number of traits is determined at runtime, the size of this data -- the number of SubstitutionTraitInfo records kept +// by each substitution -- is also determined at runtime. This is parallel to the MutationTraitInfo struct for mutations, +// but keeps less information since it is not used during fitness evaluation. Also unlike Mutation, which keeps all this +// in a block maintained by MutationBlock, we simply make a malloced block for each substitution; substitution is relatively +// rare and substitutions don't go away once created, so there is no need to overcomplicate this design. +typedef struct _SubstitutionTraitInfo +{ + slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) + slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default +} SubstitutionTraitInfo; class Substitution : public EidosDictionaryRetained { @@ -59,14 +71,17 @@ class Substitution : public EidosDictionaryRetained const slim_mutationid_t mutation_id_; // a unique id for each mutation, used to track mutations slim_usertag_t tag_value_; // a user-defined tag value + // Per-trait information + SubstitutionTraitInfo *trait_info_; // OWNED: a malloced block of per-trait information + Substitution(const Substitution&) = delete; // no copying Substitution& operator=(const Substitution&) = delete; // no copying Substitution(void) = delete; // no null construction Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick); // construct from the mutation that has fixed, and the tick in which it fixed Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide); - // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline - inline virtual ~Substitution(void) override { } + // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though + inline virtual ~Substitution(void) override { free(trait_info_); trait_info_ = nullptr; } void PrintForSLiMOutput(std::ostream &p_out) const; void PrintForSLiMOutput_Tag(std::ostream &p_out) const; From f11417a9ece3fbe9eecb89a849539d09f6c3cf93 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 18 Oct 2025 10:44:56 -0400 Subject: [PATCH 020/107] trait effect/dominance properties/methods for Mutation and Substitution --- QtSLiM/help/SLiMHelpClasses.html | 17 +- SLiMgui/SLiMHelpClasses.rtf | 151 +++++++++- VERSIONS | 4 + core/chromosome.cpp | 4 +- core/haplosome.cpp | 6 +- core/individual.cpp | 6 +- core/mutation.cpp | 497 +++++++++++++++++++++++++++++++ core/mutation.h | 8 + core/mutation_type.cpp | 16 +- core/mutation_type.h | 3 +- core/slim_eidos_block.cpp | 23 +- core/slim_globals.cpp | 4 + core/slim_globals.h | 8 + core/slim_test_genetics.cpp | 69 ++++- core/species.cpp | 4 +- core/species_eidos.cpp | 93 +++++- core/substitution.cpp | 94 ++++++ core/substitution.h | 2 + eidos/eidos_globals.h | 2 +- 19 files changed, 969 insertions(+), 42 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 6786b790..8575e3d7 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -498,7 +498,7 @@

See also sharedParentCount() for a different metric of relatedness.

+ (void)setOffsetForTrait([Nio<Trait> trait = NULL], [Nif offset = NULL])

Sets the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

-

The parameter offset must follow one of four patterns.  In the first pattern, offset is NULL; this draws the offset for each of the specified traits from each trait’s individual-offset distribution (defined by each trait’s individualOffsetMean and individualOffsetSD properties) in each target individual.  (Note that individual offsets are automatically drawn from these distributions when an individual is created; this re-draws new offset values.)  In the second pattern, offset is a singleton value; this sets the given offset for each of the specified traits in each target individual.  In the third pattern, offset is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual.  In the fourth pattern, offset is of length equal to the number of specified traits times the number of target individuals; this uses offset to provide a different offset value for each trait in each individual, using consecutive values from offset to set the offset for each of the specified traits in one individual before moving to the next individual.

+

The parameter offset must follow one of four patterns.  In the first pattern, offset is NULL; this draws the offset for each of the specified traits from each trait’s individual-offset distribution (defined by each trait’s individualOffsetMean and individualOffsetSD properties) in each target individual.  (Note that individual offsets are automatically drawn from these distributions when an individual is created; this re-draws new offset values.)  In the second pattern, offset is a singleton value; this sets the given offset for each of the specified traits in each target individual.  In the third pattern, offset is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual.  In the fourth pattern, offset is of length equal to the number of specified traits times the number of target individuals; this uses offset to provide a different offset value for each trait in each individual, using consecutive values from offset to set the offset for each of the specified traits in one individual before moving to the next individual.

+ (void)setSpatialPosition(float position)

Sets the spatial position of the individual (as accessed through the spatialPosition property).  The length of position (the number of coordinates in the spatial position of an individual) depends upon the spatial dimensionality declared with initializeSLiMOptions().  If the spatial dimensionality is zero (as it is by default), it is an error to call this method.  The elements of position are set into the values of the x, y, and z properties (if those properties are encompassed by the spatial dimensionality of the simulation).  In other words, if the declared dimensionality is "xy", calling individual.setSpatialPosition(c(1.0, 0.5)) property is equivalent to individual.x = 1.0; individual.y = 0.5; individual.z is not set (even if a third value is supplied in position) since it is not encompassed by the simulation’s dimensionality in this example.

Note that this is an Eidos class method, somewhat unusually, which allows it to work in a special way when called on a vector of individuals.  When the target vector of individuals is non-singleton, this method can do one of two things.  If position contains just a single point (i.e., is equal in length to the spatial dimensionality of the model), the spatial position of all of the target individuals will be set to the given point.  Alternatively, if position contains one point per target individual (i.e., is equal in length to the number of individuals multiplied by the spatial dimensionality of the model), the spatial position of each target individual will be set to the corresponding point from position (where the point data is concatenated, not interleaved, just as it would be returned by accessing the spatialPosition property on the vector of target individuals).  Calling this method with a position vector of any other length is an error.

@@ -698,9 +698,19 @@

tag <–> (integer$)

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.

5.10.2  Mutation methods

+

– (float)dominanceForTrait([Nio<Trait> trait = NULL])

+

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)effectForTrait([Nio<Trait> trait = NULL])

+

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

– (void)setDominanceCoeff(float$ dominanceCoeff)

Set the dominance coefficient of the mutation to dominanceCoeff.  The dominance coefficient will be changed for all individuals that possess the mutation, since they all share a single Mutation object (note that the selection coefficient will remain unchanged).

Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

+

+ (void)setDominanceForTrait([Nio<Trait> trait = NULL], [Nif dominance = NULL])

+

Sets the mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.)  In the second pattern, dominance is a singleton value; this sets the given dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the dominance for each of the specified traits in one mutation before moving to the next mutation.

+

+ (void)setEffectForTrait([Nio<Trait> trait = NULL], [Nif effect = NULL])

+

Sets the mutation’s effect(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

The parameter effect must follow one of four patterns.  In the first pattern, effect is NULL; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation.  (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.)  In the second pattern, effect is a singleton value; this sets the given effect for each of the specified traits in each target mutation.  In the third pattern, effect is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation.  In the fourth pattern, effect is of length equal to the number of specified traits times the number of target mutations; this uses effect to provide a different effect value for each trait in each mutation, using consecutive values from effect to set the effect for each of the specified traits in one mutation before moving to the next mutation.

– (void)setMutationType(io<MutationType>$ mutType)

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  The selection coefficients and dominance coefficients of existing mutations are not changed, since they are properties of the mutation objects themselves; they can be changed explicitly using the setSelectionCoeff() and setDominanceCoeff() methods of Mutation if so desired.

In nucleotide-based models, a restriction applies: nucleotide-based mutations may not be changed to a non-nucleotide-based mutation type, and non-nucleotide-based mutations may not be changed to a nucleotide-based mutation type.

@@ -1345,7 +1355,10 @@

tag <–> (integer$)

A user-defined integer value.  The value of tag is carried over automatically from the original Mutation object.  Apart from that, the value of tag is not used by SLiM; it is free for you to use.

5.18.2  Substitution methods

-


+

– (float)dominanceForTrait([Nio<Trait> trait = NULL])

+

Returns the substitution’s dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)effectForTrait([Nio<Trait> trait = NULL])

+

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

5.19  Class Trait

5.19.1  Trait properties

baselineOffset <–> (float$)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 5e03a22a..b7b60a39 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -4183,10 +4183,11 @@ See also \f4\fs20 objects; \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 The parameter +The parameter +\f3\fs18 offset +\f4\fs20 must follow one of four patterns. In the first pattern, \f3\fs18 offset -\f4\fs20 must follow one of four patterns. In the first pattern, offset is +\f4\fs20 is \f3\fs18 NULL \f4\fs20 ; this draws the offset for each of the specified traits from each trait\'92s individual-offset distribution (defined by each trait\'92s \f3\fs18 individualOffsetMean @@ -6146,7 +6147,45 @@ If you don\'92t care which subpopulation a mutation originated in, the \f1\fs22 methods\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 \'96\'a0(void)setDominanceCoeff(float$\'a0dominanceCoeff)\ +\f3\i0\fs18 \cf2 \'96\'a0(float)dominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the mutation\'92s dominance coefficient for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 ; for both multiplicative traits and additive traits this is the dominance coefficient +\f1\i h +\f4\i0 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the mutation\'92s effect size for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 ; for multiplicative traits, this is typically the selection coefficient +\f1\i s +\f4\i0 , whereas for additive traits it is typically the additive effect size +\f1\i a +\f4\i0 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(void)setDominanceCoeff(float$\'a0dominanceCoeff)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Set the dominance coefficient of the mutation to @@ -6161,6 +6200,68 @@ Changing this will normally affect the fitness values calculated toward the end \f4\fs20 \'96 but see the documentation of that method for caveats.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 +\'a0(void)setDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Sets the mutation\'92s dominance coefficient(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 dominance +\f4\fs20 must follow one of four patterns. In the first pattern, +\f3\fs18 dominance +\f4\fs20 is +\f3\fs18 NULL +\f4\fs20 ; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation. (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.) In the second pattern, +\f3\fs18 dominance +\f4\fs20 is a singleton value; this sets the given dominance for each of the specified traits in each target mutation. In the third pattern, +\f3\fs18 dominance +\f4\fs20 is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation. In the fourth pattern, +\f3\fs18 dominance +\f4\fs20 is of length equal to the number of specified traits times the number of target mutations; this uses +\f3\fs18 dominance +\f4\fs20 to provide a different dominance coefficient for each trait in each mutation, using consecutive values from +\f3\fs18 dominance +\f4\fs20 to set the dominance for each of the specified traits in one mutation before moving to the next mutation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 +\'a0(void)setEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0effect\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Sets the mutation\'92s effect(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 effect +\f4\fs20 must follow one of four patterns. In the first pattern, +\f3\fs18 effect +\f4\fs20 is +\f3\fs18 NULL +\f4\fs20 ; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation. (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.) In the second pattern, +\f3\fs18 effect +\f4\fs20 is a singleton value; this sets the given effect for each of the specified traits in each target mutation. In the third pattern, +\f3\fs18 effect +\f4\fs20 is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation. In the fourth pattern, +\f3\fs18 effect +\f4\fs20 is of length equal to the number of specified traits times the number of target mutations; this uses +\f3\fs18 effect +\f4\fs20 to provide a different effect value for each trait in each mutation, using consecutive values from +\f3\fs18 effect +\f4\fs20 to set the effect for each of the specified traits in one mutation before moving to the next mutation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \'96\'a0(void)setMutationType(io$\'a0mutType)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -13912,12 +14013,48 @@ nucleotide <\'96> (string$)\ \f1\i\fs22 \cf0 5.18.2 \f2\fs18 Substitution \f1\fs22 methods\ -\pard\pardeftab397\fi274\ri720\sb40\sa40\partightenfactor0 +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f5\i0 \cf0 \ +\f3\i0\fs18 \cf2 \'96\'a0(float)dominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the substitution\'92s dominance coefficient for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 , carried over from the original mutation object. For both multiplicative traits and additive traits this is the dominance coefficient +\f1\i h +\f4\i0 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 . +\f5\fs22 \cf0 \ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the substitution\'92s effect size for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 , carried over from the original mutation object. For multiplicative traits, this is typically the selection coefficient +\f1\i s +\f4\i0 , whereas for additive traits it is typically the additive effect size +\f1\i a +\f4\i0 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effects for a given target substitution will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 -\f0\b \cf2 5.19 Class Trait\ +\f0\b\fs22 \cf2 5.19 Class Trait\ \pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 \f1\i\b0 \cf2 5.19.1 diff --git a/VERSIONS b/VERSIONS index 3acc5ece..c7e472f8 100644 --- a/VERSIONS +++ b/VERSIONS @@ -60,6 +60,10 @@ multitrait branch: shift from a single global mutation block into per-species mutation blocks, and make a new C++ class, MutationBlock, to encapsulate this this is a forced move because we want the mutation block to have a separate buffer of per-trait state for mutations, and the number of traits varies among species add effect size and dominance coefficient properties to Mutation and Substitution (but not hooked up to the simulation yet) + add -effectForTrait([Nio traits = NULL]) and -dominanceForTrait([Nio traits = NULL]) methods to Mutation and Substitution + add +setEffectForTrait([Nio traits = NULL], [Nif effect = NULL]) and +setDominanceForTrait([Nio traits = NULL], [Nif dominance = NULL]) methods to Mutation + add Effect and Dominance properties to Mutation, both read-write float$ + add Effect and Dominance properties to Substitution, both read-only float$ version 5.1 (Eidos version 4.1): diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 7f66b066..ed6b323b 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1045,7 +1045,7 @@ MutationIndex Chromosome::DrawNewMutation(std::pairmutation_buffer_ + new_mut_index; - new (mutation) Mutation(mutation_type_ptr, index_, p_position.first, selection_coeff, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, p_subpop_index, p_tick, -1); // FIXME MULTITRAIT + new (mutation) Mutation(mutation_type_ptr, index_, p_position.first, selection_coeff, mutation_type_ptr->DefaultDominanceForTrait(0), p_subpop_index, p_tick, -1); // FIXME MULTITRAIT // addition to the main registry and the muttype registries will happen if the new mutation clears the stacking policy @@ -1414,7 +1414,7 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pairNewMutationFromBlock(); Mutation *mutation = mutation_block->mutation_buffer_ + new_mut_index; - new (mutation) Mutation(mutation_type_ptr, index_, position, selection_coeff, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, p_subpop_index, p_tick, nucleotide); // FIXME MULTITRAIT + new (mutation) Mutation(mutation_type_ptr, index_, position, selection_coeff, mutation_type_ptr->DefaultDominanceForTrait(0), p_subpop_index, p_tick, nucleotide); // FIXME MULTITRAIT // Call mutation() callbacks if there are any if (p_mutation_callbacks) diff --git a/core/haplosome.cpp b/core/haplosome.cpp index e44ff566..2b8e19bc 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2907,7 +2907,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); - Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, origin_subpop_id, origin_tick, (int8_t)nucleotide); // FIXME MULTITRAIT + Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); // FIXME MULTITRAIT // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DFE), so we set all_pure_neutral_DFE_ also @@ -3407,7 +3407,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); - Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_, subpop_index, origin_tick, nucleotide); // FIXME MULTITRAIT + Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); // FIXME MULTITRAIT // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ if (selection_coeff != 0.0) @@ -3966,7 +3966,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt if (info_domcoeffs.size() > 0) dominance_coeff = info_domcoeffs[alt_allele_index]; else - dominance_coeff = mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_; // FIXME MULTITRAIT + dominance_coeff = mutation_type_ptr->DefaultDominanceForTrait(0); // FIXME MULTITRAIT // get the selection coefficient from S, or draw one from the mutation type slim_effect_t selection_coeff; diff --git a/core/individual.cpp b/core/individual.cpp index ee5e6c2c..0dfbd23c 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -4202,7 +4202,7 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_relatedness, kEidosValueMaskFloat))->AddObject("individuals", gSLiM_Individual_Class)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, "chromosome", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_haplosomesForChromosomes, kEidosValueMaskObject, gSLiM_Haplosome_Class))->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddInt_OSN("index", gStaticEidosValueNULL)->AddLogical_OS("includeNulls", gStaticEidosValue_LogicalT)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_uniqueMutationsOfType, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->MarkDeprecated()); @@ -4264,7 +4264,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin // get the trait indices, with bounds-checking std::vector trait_indices; - species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "offsetForTrait"); + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setOffsetForTrait"); int trait_count = (int)trait_indices.size(); if (offset_value->Type() == EidosValueType::kValueNULL) @@ -5013,7 +5013,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (info_domcoeffs.size() > 0) dominance_coeff = info_domcoeffs[alt_allele_index]; else - dominance_coeff = mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_; // FIXME MULTITRAIT + dominance_coeff = static_cast(mutation_type_ptr->DefaultDominanceForTrait(0)); // FIXME MULTITRAIT // get the selection coefficient from S, or draw one from the mutation type slim_effect_t selection_coeff; diff --git a/core/mutation.cpp b/core/mutation.cpp index 1c7322af..4484e9f9 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -21,6 +21,7 @@ #include "mutation.h" #include "eidos_call_signature.h" #include "eidos_property_signature.h" +#include "community.h" #include "species.h" #include "mutation_block.h" @@ -312,6 +313,30 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: + // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationIndex mut_index = mutation_block->IndexInBlock(this); + MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); + + if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 6); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info[trait->Index()].effect_size_)); + } + else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 9); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info[trait->Index()].dominance_coeff_)); + } + return super::GetProperty(p_property_id); } } @@ -563,6 +588,36 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & default: { + // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationIndex mut_index = mutation_block->IndexInBlock(this); + MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); + + if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 6); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + { + trait_info[trait->Index()].effect_size_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + return; + } + } + else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 9); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + { + trait_info[trait->Index()].dominance_coeff_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + return; + } + } + return super::SetProperty(p_property_id, p_value); } } @@ -611,6 +666,8 @@ EidosValue_SP Mutation::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, c { switch (p_method_id) { + case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_setSelectionCoeff: return ExecuteMethod_setSelectionCoeff(p_method_id, p_arguments, p_interpreter); case gID_setDominanceCoeff: return ExecuteMethod_setDominanceCoeff(p_method_id, p_arguments, p_interpreter); case gID_setMutationType: return ExecuteMethod_setMutationType(p_method_id, p_arguments, p_interpreter); @@ -618,6 +675,84 @@ EidosValue_SP Mutation::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, c } } +// ********************* - (float)effectForTrait([Nio trait = NULL]) +// +EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectForTrait"); + + // get the trait info for this mutation + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationIndex mut_index = mutation_block->IndexInBlock(this); + MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t effect = trait_info[trait_index].effect_size_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(effect)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t effect = trait_info[trait_index].effect_size_; + + float_result->push_float_no_check(effect); + } + + return EidosValue_SP(float_result); + } +} + +// ********************* - (float)dominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "dominanceForTrait"); + + // get the trait info for this mutation + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationIndex mut_index = mutation_block->IndexInBlock(this); + MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t dominance = trait_info[trait_index].dominance_coeff_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t dominance = trait_info[trait_index].dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } +} + // ********************* - (void)setSelectionCoeff(float$ selectionCoeff) // EidosValue_SP Mutation::ExecuteMethod_setSelectionCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -760,6 +895,10 @@ const std::vector *Mutation_Class::Methods(void) const methods = new std::vector(*super::Methods()); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setSelectionCoeff, kEidosValueMaskVOID))->AddFloat_S("selectionCoeff")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDominanceCoeff, kEidosValueMaskVOID))->AddFloat_S("dominanceCoeff")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setMutationType, kEidosValueMaskVOID))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); @@ -770,6 +909,364 @@ const std::vector *Mutation_Class::Methods(void) const return methods; } +EidosValue_SP Mutation_Class::ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ + switch (p_method_id) + { + case gID_setEffectForTrait: return ExecuteMethod_setEffectForTrait(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setDominanceForTrait: return ExecuteMethod_setDominanceForTrait(p_method_id, p_target, p_arguments, p_interpreter); + default: + return super::ExecuteClassMethod(p_method_id, p_target, p_arguments, p_interpreter); + } +} + +// ********************* + (void)setEffectForTrait([Nio trait = NULL], [Nif effect = NULL]) +// +EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *effect_value = p_arguments[1].get(); + + int mutations_count = p_target->Count(); + int effect_count = effect_value->Count(); + + if (mutations_count == 0) + return gStaticEidosValueVOID; + + Mutation **mutations_buffer = (Mutation **)p_target->ObjectData(); + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForMutations(p_target); + + if (!species) + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): setEffectForTrait() requires that all mutations belong to the same species." << EidosTerminate(); + + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setEffectForTrait"); + int trait_count = (int)trait_indices.size(); + + if (effect_value->Type() == EidosValueType::kValueNULL) + { + // pattern 1: drawing a default effect value for each trait in one or more mutations + for (int64_t trait_index : trait_indices) + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationType *muttype = mut->mutation_type_ptr_; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + slim_effect_t effect = (slim_effect_t)muttype->DrawEffectForTrait(trait_index); + + mut_trait_info[trait_index].effect_size_ = effect; + } + } + } + else if (effect_count == 1) + { + // pattern 2: setting a single effect value across one or more traits in one or more mutations + slim_effect_t effect = static_cast(effect_value->NumericAtIndex_NOCAST(0, nullptr)); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + mut_trait_info[trait_index].effect_size_ = effect; + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + for (int64_t trait_index : trait_indices) + mut_trait_info[trait_index].effect_size_ = effect; + } + } + } + else if (effect_count == trait_count) + { + // pattern 3: setting one effect value per trait, in one or more mutations + int effect_index = 0; + + for (int64_t trait_index : trait_indices) + { + slim_effect_t effect = static_cast(effect_value->NumericAtIndex_NOCAST(effect_index++, nullptr)); + + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + mut_trait_info[trait_index].effect_size_ = effect; + } + } + } + else if (effect_count == trait_count * mutations_count) + { + // pattern 4: setting different effect values for each trait in each mutation; in this case, + // all effects for the specified traits in a given mutation are given consecutively + if (effect_value->Type() == EidosValueType::kValueInt) + { + // integer effect values + const int64_t *effects_int = effect_value->IntData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + mut_trait_info[trait_index].effect_size_ = static_cast(*(effects_int++)); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + for (int64_t trait_index : trait_indices) + mut_trait_info[trait_index].effect_size_ = static_cast(*(effects_int++)); + } + } + } + else + { + // float effect values + const double *effects_float = effect_value->FloatData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + mut_trait_info[trait_index].effect_size_ = static_cast(*(effects_float++)); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + for (int64_t trait_index : trait_indices) + mut_trait_info[trait_index].effect_size_ = static_cast(*(effects_float++)); + } + } + } + } + else + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): setEffectForTrait() requires that effect be (a) NULL, requesting an effect value drawn from the mutation's mutation type for each trait, (b) singleton, providing one effect value for all traits, (c) equal in length to the number of traits in the species, providing one effect value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one effect value per trait per mutation." << EidosTerminate(); + + return gStaticEidosValueVOID; +} + +// ********************* + (void)setDominanceForTrait([Nio trait = NULL], [Nif dominance = NULL]) +// +EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *dominance_value = p_arguments[1].get(); + + int mutations_count = p_target->Count(); + int dominance_count = dominance_value->Count(); + + if (mutations_count == 0) + return gStaticEidosValueVOID; + + Mutation **mutations_buffer = (Mutation **)p_target->ObjectData(); + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForMutations(p_target); + + if (!species) + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setDominanceForTrait): setDominanceForTrait() requires that all mutations belong to the same species." << EidosTerminate(); + + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDominanceForTrait"); + int trait_count = (int)trait_indices.size(); + + // note there is intentionally no bounds check of dominance coefficients + if (dominance_value->Type() == EidosValueType::kValueNULL) + { + // pattern 1: drawing a default dominance value for each trait in one or more mutations + for (int64_t trait_index : trait_indices) + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationType *muttype = mut->mutation_type_ptr_; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + slim_effect_t dominance = (slim_effect_t)muttype->DefaultDominanceForTrait(trait_index); + + mut_trait_info[trait_index].dominance_coeff_ = dominance; + } + } + } + else if (dominance_count == 1) + { + // pattern 2: setting a single dominance value across one or more traits in one or more mutations + slim_effect_t dominance = static_cast(dominance_value->NumericAtIndex_NOCAST(0, nullptr)); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + mut_trait_info[trait_index].dominance_coeff_ = dominance; + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + for (int64_t trait_index : trait_indices) + mut_trait_info[trait_index].dominance_coeff_ = dominance; + } + } + } + else if (dominance_count == trait_count) + { + // pattern 3: setting one dominance value per trait, in one or more mutations + int dominance_index = 0; + + for (int64_t trait_index : trait_indices) + { + slim_effect_t dominance = static_cast(dominance_value->NumericAtIndex_NOCAST(dominance_index++, nullptr)); + + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + mut_trait_info[trait_index].dominance_coeff_ = dominance; + } + } + } + else if (dominance_count == trait_count * mutations_count) + { + // pattern 4: setting different dominance values for each trait in each mutation; in this case, + // all dominances for the specified traits in a given mutation are given consecutively + if (dominance_value->Type() == EidosValueType::kValueInt) + { + // integer dominance values + const int64_t *dominances_int = dominance_value->IntData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + mut_trait_info[trait_index].dominance_coeff_ = static_cast(*(dominances_int++)); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + for (int64_t trait_index : trait_indices) + mut_trait_info[trait_index].dominance_coeff_ = static_cast(*(dominances_int++)); + } + } + } + else + { + // float dominance values + const double *dominances_float = dominance_value->FloatData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + mut_trait_info[trait_index].dominance_coeff_ = static_cast(*(dominances_float++)); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationIndex mut_index = mutation_block->IndexInBlock(mut); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + + for (int64_t trait_index : trait_indices) + mut_trait_info[trait_index].dominance_coeff_ = static_cast(*(dominances_float++)); + } + } + } + } + else + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setDominanceForTrait): setDominanceForTrait() requires that dominance be (a) NULL, requesting the default dominance coefficient from the mutation's mutation type for each trait, (b) singleton, providing one dominance value for all traits, (c) equal in length to the number of traits in the species, providing one dominance value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one dominance value per trait per mutation." << EidosTerminate(); + + return gStaticEidosValueVOID; +} + + + diff --git a/core/mutation.h b/core/mutation.h index 581608d3..f40260cc 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -145,6 +145,10 @@ class Mutation : public EidosDictionaryRetained virtual EidosValue_SP GetProperty(EidosGlobalStringID p_property_id) override; virtual void SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) override; virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; + EidosValue_SP ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + EidosValue_SP ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_setSelectionCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setDominanceCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); @@ -196,6 +200,10 @@ class Mutation_Class : public EidosDictionaryRetained_Class virtual const std::vector *Properties(void) const override; virtual const std::vector *Methods(void) const override; + + virtual EidosValue_SP ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const override; + EidosValue_SP ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + EidosValue_SP ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; }; #endif /* defined(__SLiM__mutation__) */ diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 061af4d1..44e38b7d 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -247,6 +247,13 @@ void MutationType::ParseDFEParameters(std::string &p_dfe_type_string, const Eido } } +double MutationType::DefaultDominanceForTrait(int64_t p_trait_index) const +{ + const EffectDistributionInfo &de_info = effect_distributions_[p_trait_index]; + + return de_info.default_dominance_coeff_; +} + double MutationType::DrawEffectForTrait(int64_t p_trait_index) const { const EffectDistributionInfo &de_info = effect_distributions_[p_trait_index]; @@ -720,20 +727,15 @@ EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalSt if (trait_indices.size() == 1) { int64_t trait_index = trait_indices[0]; - EffectDistributionInfo &de_info = effect_distributions_[trait_index]; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(de_info.default_dominance_coeff_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(DefaultDominanceForTrait(trait_index))); } else { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); for (int64_t trait_index : trait_indices) - { - EffectDistributionInfo &de_info = effect_distributions_[trait_index]; - - float_result->push_float_no_check(de_info.default_dominance_coeff_); - } + float_result->push_float_no_check(DefaultDominanceForTrait(trait_index)); return EidosValue_SP(float_result); } diff --git a/core/mutation_type.h b/core/mutation_type.h index 3af09600..7a81706c 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -182,7 +182,8 @@ class MutationType : public EidosDictionaryUnretained static void ParseDFEParameters(std::string &p_dfe_type_string, const EidosValue_SP *const p_arguments, int p_argument_count, DFEType *p_dfe_type, std::vector *p_dfe_parameters, std::vector *p_dfe_strings); - double DrawEffectForTrait(int64_t p_trait_index) const; // draw a selection coefficient from the DE for a trait + double DefaultDominanceForTrait(int64_t p_trait_index) const; // get the default dominance coefficient for a trait + double DrawEffectForTrait(int64_t p_trait_index) const; // draw a selection coefficient from the DE for a trait bool IsPureNeutralDFE(void) const { return all_pure_neutral_DFE_; } diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index a45ca8b3..1ce2a4df 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -2006,16 +2006,27 @@ EidosTypeSpecifier SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(std: if (trait_name_token->token_type_ == EidosTokenType::kTokenString) { - // initializeTrait() has the side effect of defining dynamic properties on Species and Individual; - // we need to set up the information needed to make that work with code completion; we do that + // initializeTrait() has the side effect of defining dynamic properties on various classes; + // we need to set up the information needed to make that work with code completion. We do that // with AddSignatureForProperty_TYPE_INTERPRETER(), a version of AddSignatureForProperty() that // uses scratch space belonging only to us, so we don't interfere with anything in SLiM itself. const std::string &trait_name = trait_name_token->token_string_; - EidosPropertySignature_CSP species_signature((new EidosPropertySignature(trait_name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); - EidosPropertySignature_CSP individual_signature((new EidosPropertySignature(trait_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + const std::string &traitEffect_name = trait_name + "Effect"; + const std::string &traitDominance_name = trait_name + "Dominance"; - gSLiM_Species_Class->AddSignatureForProperty_TYPE_INTERPRETER(species_signature); - gSLiM_Individual_Class->AddSignatureForProperty_TYPE_INTERPRETER(individual_signature); + EidosPropertySignature_CSP species_trait_signature((new EidosPropertySignature(trait_name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP individual_trait_signature((new EidosPropertySignature(trait_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP mutation_traitEffect_signature((new EidosPropertySignature(traitEffect_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP mutation_traitDominance_signature((new EidosPropertySignature(traitDominance_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP substitution_traitEffect_signature((new EidosPropertySignature(traitEffect_name, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP substitution_traitDominance_signature((new EidosPropertySignature(traitDominance_name, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + + gSLiM_Species_Class->AddSignatureForProperty_TYPE_INTERPRETER(species_trait_signature); + gSLiM_Individual_Class->AddSignatureForProperty_TYPE_INTERPRETER(individual_trait_signature); + gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitEffect_signature); + gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitDominance_signature); + gSLiM_Substitution_Class->AddSignatureForProperty_TYPE_INTERPRETER(substitution_traitEffect_signature); + gSLiM_Substitution_Class->AddSignatureForProperty_TYPE_INTERPRETER(substitution_traitDominance_signature); } } diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 16f3790d..9f5b2dca 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1377,6 +1377,10 @@ const std::string &gStr_removeMutations = EidosRegisteredString("removeMutations const std::string &gStr_setGenomicElementType = EidosRegisteredString("setGenomicElementType", gID_setGenomicElementType); const std::string &gStr_setMutationFractions = EidosRegisteredString("setMutationFractions", gID_setMutationFractions); const std::string &gStr_setMutationMatrix = EidosRegisteredString("setMutationMatrix", gID_setMutationMatrix); +const std::string &gStr_effectForTrait = EidosRegisteredString("effectForTrait", gID_effectForTrait); +const std::string &gStr_dominanceForTrait = EidosRegisteredString("dominanceForTrait", gID_dominanceForTrait); +const std::string &gStr_setEffectForTrait = EidosRegisteredString("setEffectForTrait", gID_setEffectForTrait); +const std::string &gStr_setDominanceForTrait = EidosRegisteredString("setDominanceForTrait", gID_setDominanceForTrait); const std::string &gStr_setSelectionCoeff = EidosRegisteredString("setSelectionCoeff", gID_setSelectionCoeff); const std::string &gStr_setDominanceCoeff = EidosRegisteredString("setDominanceCoeff", gID_setDominanceCoeff); const std::string &gStr_setMutationType = EidosRegisteredString("setMutationType", gID_setMutationType); diff --git a/core/slim_globals.h b/core/slim_globals.h index a2a7c236..5d4365f4 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -967,6 +967,10 @@ extern const std::string &gStr_removeMutations; extern const std::string &gStr_setGenomicElementType; extern const std::string &gStr_setMutationFractions; extern const std::string &gStr_setMutationMatrix; +extern const std::string &gStr_effectForTrait; +extern const std::string &gStr_dominanceForTrait; +extern const std::string &gStr_setEffectForTrait; +extern const std::string &gStr_setDominanceForTrait; extern const std::string &gStr_setSelectionCoeff; extern const std::string &gStr_setDominanceCoeff; extern const std::string &gStr_setMutationType; @@ -1444,6 +1448,10 @@ enum _SLiMGlobalStringID : int { gID_setGenomicElementType, gID_setMutationFractions, gID_setMutationMatrix, + gID_effectForTrait, + gID_dominanceForTrait, + gID_setEffectForTrait, + gID_setDominanceForTrait, gID_setSelectionCoeff, gID_setDominanceCoeff, gID_setMutationType, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 40ee2963..018c1cf9 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -950,7 +950,7 @@ R"V0G0N( initialize() { defineConstant("T_height", initializeTrait("height", "multiplicative", 2.0)); defineConstant("T_weight", initializeTrait("weight", "additive", 186.0)); - initializeMutationRate(1e-7); + initializeMutationRate(1e-5); initializeMutationType("m1", 0.5, "f", 0.0); initializeGenomicElementType("g1", m1, 1.0); initializeGenomicElement(g1, 0, 99999); @@ -966,8 +966,8 @@ initialize() { initializeSLiMModelType("nonWF"); defineConstant("T_height", initializeTrait("height", "multiplicative", 2.0)); defineConstant("T_weight", initializeTrait("weight", "additive", 186.0)); - initializeMutationRate(1e-7); - initializeMutationType("m1", 0.5, "f", 0.0); + initializeMutationRate(1e-5); + initializeMutationType("m1", 0.5, "f", 0.0).convertToSubstitution = T; initializeGenomicElementType("g1", m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); @@ -1019,7 +1019,7 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(c(T_weight, T_height), sim.traitsWithNames(c('weight', 'height')))) stop(); }"); SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.traitsWithNames('typo'); }", "trait with the given name (typo)", __LINE__); - // basic trait properties + // basic trait properties: baselineOffset, directFitnessEffect, index, individualOffsetMean, individualOffsetSD, name, species, tag, type SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.baselineOffset, 2.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.baselineOffset, 186.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.baselineOffset = 12.5; if (!identical(T_height.baselineOffset, 12.5)) stop(); }"); @@ -1087,6 +1087,67 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 10.0; if (!identical(p1.individuals.weight, rep(10.0, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0:14; if (!identical(p1.individuals.height, 10.0:14)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 11.0:15; if (!identical(p1.individuals.weight, 11.0:15)) stop(); }"); + + // Mutation effectForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectForTrait(0), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectForTrait(1), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectForTrait(NULL), c(0.0, 0.0))) stop(); }"); + + // Mutation setEffectForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, 3); mut.setEffectForTrait(1, 4.5); if (!identical(mut.effectForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, 1:5 * 2 - 1); mut.setEffectForTrait(1, 1:5 * 2); if (!identical(mut.effectForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(NULL, 1:10); if (!identical(mut.effectForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(NULL, 1:10 + 0.5); if (!identical(mut.effectForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(0,1), 1:10); if (!identical(mut.effectForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(0,1), 1:10 + 0.5); if (!identical(mut.effectForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(1,0), 1:10); if (!identical(mut.effectForTrait(c(1,0)), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.effectForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + + // Mutation dominanceForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(0), 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(1), 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(NULL), c(0.5, 0.5))) stop(); }"); + + // Mutation setDominanceForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(0, 3); mut.setDominanceForTrait(1, 4.5); if (!identical(mut.dominanceForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(0, 1:5 * 2 - 1); mut.setDominanceForTrait(1, 1:5 * 2); if (!identical(mut.dominanceForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(NULL, 1:10); if (!identical(mut.dominanceForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(NULL, 1:10 + 0.5); if (!identical(mut.dominanceForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(0,1), 1:10); if (!identical(mut.dominanceForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(0,1), 1:10 + 0.5); if (!identical(mut.dominanceForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(1,0), 1:10); if (!identical(mut.dominanceForTrait(c(1,0)), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.dominanceForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + + // Substitution effectForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(0), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(1), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(NULL), c(0.0, 0.0))) stop(); }"); + + // Substitution dominanceForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(0), 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(1), 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(NULL), c(0.5, 0.5))) stop(); }"); + + // Mutation Effect property + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightEffect, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightEffect, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffect = 0.25; if (!identical(mut.heightEffect, 0.25)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightEffect = 0.25; if (!identical(mut.weightEffect, 0.25)) stop(); }"); + + // Mutation Dominance property + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightDominance, 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightDominance, 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightDominance = 0.25; if (!identical(mut.heightDominance, 0.25)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightDominance = 0.25; if (!identical(mut.weightDominance, 0.25)) stop(); }"); + + // Substitution Effect property + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.heightEffect, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.weightEffect, 0.0)) stop(); }"); + + // Substitution Dominance property + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.heightDominance, 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.weightDominance, 0.5)) stop(); }"); + } std::cout << "_RunMultitraitTests() done" << std::endl; diff --git a/core/species.cpp b/core/species.cpp index 070b87d8..a6da178f 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -9827,7 +9827,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapeffect_distributions_[0].default_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); // FIXME MULTITRAIT + Substitution *sub = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->DefaultDominanceForTrait(0) /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); // FIXME MULTITRAIT population_.treeseq_substitutions_map_.emplace(position, sub); population_.substitutions_.emplace_back(sub); @@ -9841,7 +9841,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapNewMutationFromBlock(); // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec - Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->effect_distributions_[0].default_dominance_coeff_ /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); // FIXME MULTITRAIT + Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->DefaultDominanceForTrait(0) /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); // FIXME MULTITRAIT // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry p_mutIndexMap[mutation_id] = new_mut_index; diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 54f4ca57..71eb4a21 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1696,6 +1696,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string // SLiMgui the traits from one model will show up in a different model running at the same time, and // registered trait properties will not go away when you recycle. I'm ok with that. EidosGlobalStringID trait_stringID = EidosStringRegistry::GlobalStringIDForString(name); + EidosGlobalStringID traitEffect_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Effect"); + EidosGlobalStringID traitDominance_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Dominance"); { // add a Species property that returns the trait object @@ -1706,14 +1708,15 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string // an existing signature must return a singleton Trait object etc., otherwise we have a conflict if (!existing_signature->IsDynamicWithOwner("Trait") || (existing_signature->value_mask_ != (kEidosValueMaskObject | kEidosValueMaskSingleton)) || - (existing_signature->value_class_ != gSLiM_Trait_Class) || (existing_signature->read_only_ == false)) + (existing_signature->value_class_ != gSLiM_Trait_Class) || + (existing_signature->read_only_ == false)) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Species class, but the name '" << name << "' conflicts with an existing property on Species. A different name must be used for this trait." << EidosTerminate(); // no conflict, so we don't need to do anything; a different species has already registered the property } else { - // see also SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this EidosPropertySignature_CSP signature((new EidosPropertySignature(name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class)) ->MarkAsDynamicWithOwner("Trait")); @@ -1734,7 +1737,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } else { - // see also SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this EidosPropertySignature_CSP signature((new EidosPropertySignature(name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> MarkAsDynamicWithOwner("Trait")-> DeclareAcceleratedGet(Individual::GetProperty_Accelerated_TRAIT_VALUE)-> @@ -1744,7 +1747,89 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } } - // FIXME MULTITRAIT: auto-complete on trait names off of "sim" or individuals doesn't presently work; the initializeTrait() call should add entries to the autocompletion mechanism somehow! + { + // add a Mutation property that returns the effect size for the trait in a mutation + const EidosPropertySignature *existing_signature = gSLiM_Mutation_Class->SignatureForProperty(traitEffect_stringID); + + if (existing_signature) + { + if (!existing_signature->IsDynamicWithOwner("Trait") || + (existing_signature->value_mask_ != (kEidosValueMaskFloat | kEidosValueMaskSingleton)) || + (existing_signature->read_only_ == true)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Mutation class, but the name '" << name << "' conflicts with an existing property on Mutation. A different name must be used for this trait." << EidosTerminate(); + } + else + { + // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "Effect", false, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + MarkAsDynamicWithOwner("Trait")); + + gSLiM_Mutation_Class->AddSignatureForProperty(signature); + } + } + + { + // add a Mutation property that returns the dominance for the trait in a mutation + const EidosPropertySignature *existing_signature = gSLiM_Mutation_Class->SignatureForProperty(traitDominance_stringID); + + if (existing_signature) + { + if (!existing_signature->IsDynamicWithOwner("Trait") || + (existing_signature->value_mask_ != (kEidosValueMaskFloat | kEidosValueMaskSingleton)) || + (existing_signature->read_only_ == true)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Mutation class, but the name '" << name << "' conflicts with an existing property on Mutation. A different name must be used for this trait." << EidosTerminate(); + } + else + { + // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "Dominance", false, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + MarkAsDynamicWithOwner("Trait")); + + gSLiM_Mutation_Class->AddSignatureForProperty(signature); + } + } + + { + // add a Substitution property that returns the effect size for the trait in a substitution + const EidosPropertySignature *existing_signature = gSLiM_Substitution_Class->SignatureForProperty(traitEffect_stringID); + + if (existing_signature) + { + if (!existing_signature->IsDynamicWithOwner("Trait") || + (existing_signature->value_mask_ != (kEidosValueMaskFloat | kEidosValueMaskSingleton)) || + (existing_signature->read_only_ == false)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Substitution class, but the name '" << name << "' conflicts with an existing property on Substitution. A different name must be used for this trait." << EidosTerminate(); + } + else + { + // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "Effect", true, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + MarkAsDynamicWithOwner("Trait")); + + gSLiM_Substitution_Class->AddSignatureForProperty(signature); + } + } + + { + // add a Substitution property that returns the dominance for the trait in a substitution + const EidosPropertySignature *existing_signature = gSLiM_Substitution_Class->SignatureForProperty(traitDominance_stringID); + + if (existing_signature) + { + if (!existing_signature->IsDynamicWithOwner("Trait") || + (existing_signature->value_mask_ != (kEidosValueMaskFloat | kEidosValueMaskSingleton)) || + (existing_signature->read_only_ == false)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Substitution class, but the name '" << name << "' conflicts with an existing property on Substitution. A different name must be used for this trait." << EidosTerminate(); + } + else + { + // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "Dominance", true, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + MarkAsDynamicWithOwner("Trait")); + + gSLiM_Substitution_Class->AddSignatureForProperty(signature); + } + } if (SLiM_verbosity_level >= 1) { diff --git a/core/substitution.cpp b/core/substitution.cpp index 8b1b8cca..647d6b49 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -227,6 +227,27 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: + // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. + Species &species = mutation_type_ptr_->species_; + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); + + if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 6); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].effect_size_)); + } + else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 9); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].dominance_coeff_)); + } + return super::GetProperty(p_property_id); } } @@ -472,10 +493,80 @@ EidosValue_SP Substitution::ExecuteInstanceMethod(EidosGlobalStringID p_method_i { switch (p_method_id) { + case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } } +// ********************* - (float)effectForTrait([Nio trait = NULL]) +// +EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t effect = trait_info_[trait_index].effect_size_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(effect)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t effect = trait_info_[trait_index].effect_size_; + + float_result->push_float_no_check(effect); + } + + return EidosValue_SP(float_result); + } +} + +// ********************* - (float)dominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "dominanceForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } +} + // // Substitution_Class @@ -526,6 +617,9 @@ const std::vector *Substitution_Class::Methods(void) c methods = new std::vector(*super::Methods()); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/substitution.h b/core/substitution.h index 162f02da..1b8fe141 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -95,6 +95,8 @@ class Substitution : public EidosDictionaryRetained virtual EidosValue_SP GetProperty(EidosGlobalStringID p_property_id) override; virtual void SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) override; virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; + EidosValue_SP ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); diff --git a/eidos/eidos_globals.h b/eidos/eidos_globals.h index 062d6b97..81ec0c6a 100644 --- a/eidos/eidos_globals.h +++ b/eidos/eidos_globals.h @@ -1327,7 +1327,7 @@ enum _EidosGlobalStringID : uint32_t gEidosID_Individual, gEidosID_LastEntry, // IDs added by the Context should start here - gEidosID_LastContextEntry = 555 // IDs added by the Context must end before this value; Eidos reserves the remaining values + gEidosID_LastContextEntry = 560 // IDs added by the Context must end before this value; Eidos reserves the remaining values }; extern std::vector gEidosConstantNames; // T, F, NULL, PI, E, INF, NAN From 0875937ba7795d16f7fd9d3981966d8d8a2b2522 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 18 Oct 2025 11:10:38 -0400 Subject: [PATCH 021/107] extend test runtime a bit to try to fix occasional failures --- core/slim_test_genetics.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 018c1cf9..f49ad1b8 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1119,14 +1119,14 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.dominanceForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); // Substitution effectForTrait() - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(0), 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(1), 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(NULL), c(0.0, 0.0))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(0), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(1), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(NULL), c(0.0, 0.0))) stop(); }"); // Substitution dominanceForTrait() - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(0), 0.5)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(1), 0.5)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(NULL), c(0.5, 0.5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(0), 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(1), 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(NULL), c(0.5, 0.5))) stop(); }"); // Mutation Effect property SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightEffect, 0.0)) stop(); }"); @@ -1141,12 +1141,12 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightDominance = 0.25; if (!identical(mut.weightDominance, 0.25)) stop(); }"); // Substitution Effect property - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.heightEffect, 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.weightEffect, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.heightEffect, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.weightEffect, 0.0)) stop(); }"); // Substitution Dominance property - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.heightDominance, 0.5)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "100 late() { sub = sim.substitutions[0]; if (!identical(sub.weightDominance, 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.heightDominance, 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.weightDominance, 0.5)) stop(); }"); } From 0629ef23207397ac7ccef39756acbb8aebc6d969 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 18 Oct 2025 21:40:43 -0400 Subject: [PATCH 022/107] remove setSelectionCoeff(), rename selectionCoeff to effect --- QtSLiM/QtSLiMWindow.cpp | 13 +++ QtSLiM/help/SLiMHelpClasses.html | 43 ++++---- SLiMgui/SLiMHelpClasses.rtf | 151 +++++++++++----------------- VERSIONS | 5 +- core/haplosome.cpp | 2 +- core/mutation.cpp | 163 ++++++++++++------------------- core/mutation.h | 4 - core/mutation_type.h | 1 - core/slim_functions.cpp | 2 +- core/slim_globals.cpp | 5 +- core/slim_globals.h | 10 +- core/slim_test.cpp | 2 +- core/slim_test_genetics.cpp | 10 +- core/slim_test_other.cpp | 2 +- core/substitution.cpp | 92 ++++++++++------- core/substitution.h | 2 - 16 files changed, 213 insertions(+), 294 deletions(-) diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index 79604d82..f4aa075d 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -2154,6 +2154,19 @@ bool QtSLiMWindow::checkTerminationForAutofix(QString terminationMessage) (selectionString == "drawSelectionCoefficient")) return offerAndExecuteAutofix(selection, "drawEffectForTrait", "The `drawSelectionCoefficient()` method of MutationType has become the method `drawEffectForTrait()`.", terminationMessage); + if ((afterSelection1String == "(") && + terminationMessage.contains("method setSelectionCoeff() is not defined on object element type Mutation") && + (selectionPlus1AfterString == "setSelectionCoeff(")) + return offerAndExecuteAutofix(selectionPlus1After, "setEffectForTrait(NULL, ", "The `setSelectionCoeff()` method of Mutation has become the method `setEffectForTrait()`.", terminationMessage); + + if (terminationMessage.contains("property selectionCoeff is not defined for object element type Mutation") && + (selectionString == "selectionCoeff")) + return offerAndExecuteAutofix(selection, "effect", "The `selectionCoeff` property of Mutation has become the property `effect`.", terminationMessage); + + if (terminationMessage.contains("property selectionCoeff is not defined for object element type Substitution") && + (selectionString == "selectionCoeff")) + return offerAndExecuteAutofix(selection, "effect", "The `selectionCoeff` property of Substitution has become the property `effect`.", terminationMessage); + return false; } diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 8575e3d7..6aca7c1d 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -36,10 +36,9 @@ span.s12 {text-decoration: underline ; color: #0000ff} span.s13 {font: 6.7px 'Times New Roman'} span.s14 {font: 10.0px 'Lucida Grande'} - span.s15 {font: 10.0px 'Times New Roman'; color: #000000} - span.s16 {font: 6.7px Optima} - span.s17 {font: 10.0px Optima; color: #000000} - span.s18 {font: 6.7px Optima; font-kerning: none} + span.s15 {font: 6.7px Optima} + span.s16 {font: 10.0px Optima; color: #000000} + span.s17 {font: 6.7px Optima; font-kerning: none} span.Apple-tab-span {white-space:pre} @@ -670,9 +669,12 @@

5.10.1  Mutation properties

chromosome => (object<Chromosome>$)

The Chromosome object with which the mutation is associated.

-

dominanceCoeff => (float$)

-

The dominance coefficient of the mutation, taken from the default dominance coefficient of its MutationType.  If a mutation has a selectionCoeff of s and a dominanceCoeff of h, the multiplicative fitness effect of the mutation in a homozygote is 1+s, and in a heterozygote is 1+hs.  The dominance coefficient of a mutation can be changed with the setDominanceCoeff() method.

-

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s dominance coefficient to some number x, mut.dominanceCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutations.

+

dominance => (float)

+

The dominance coefficient(s) of the mutation, taken from the default dominance coefficient(s) of its MutationType.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightDominance to access the dominance for that trait.  The dominance coefficient(s) of a mutation can be changed with the setDominanceForTrait() method.

+

Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s dominance coefficient to some number x, mut.dominanceCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

+

effect => (float)

+

The effect size(s) of the mutation, drawn from the distribution of effect sizes of its MutationType.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightEffect to access the effect for that trait.  The effect size of a mutation can be changed with the setEffectForTrait() method.

+

Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s effect size to some number x, mut.effect==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

id => (integer$)

The identifier for this mutation.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run.  These identifiers are not re-used during a run, except that if a population file is loaded from disk, the loaded mutations will receive their original identifier values as saved in the population file.

isFixed => (logical$)

@@ -689,9 +691,6 @@

The tick in which this mutation arose.

position => (integer$)

The position in the chromosome of this mutation.

-

selectionCoeff => (float$)

-

The selection coefficient of the mutation, drawn from the distribution of fitness effects of its MutationType.  If a mutation has a selectionCoeff of s, the multiplicative fitness effect of the mutation in a homozygote is 1+s; in a heterozygote it is 1+hs, where h is the dominance coefficient kept by the mutation type.

-

Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s selection coefficient to some number x, mut.selectionCoeff==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.  Instead, it is recommended to use the id or tag properties to identify particular mutations.

subpopID <–> (integer$)

The identifier of the subpopulation in which this mutation arose.  This property can be used to track the ancestry of mutations through their subpopulation of origin.

If you don’t care which subpopulation a mutation originated in, the subpopID may be used as an arbitrary integer “tag” value for any purpose you wish; SLiM does not do anything with the value of subpopID except propagate it to Substitution objects and report it in output.  (It must still be >= 0, however, since SLiM object identifiers are limited to nonnegative integers).

@@ -702,9 +701,6 @@

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

– (float)effectForTrait([Nio<Trait> trait = NULL])

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

-

– (void)setDominanceCoeff(float$ dominanceCoeff)

-

Set the dominance coefficient of the mutation to dominanceCoeff.  The dominance coefficient will be changed for all individuals that possess the mutation, since they all share a single Mutation object (note that the selection coefficient will remain unchanged).

-

Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

+ (void)setDominanceForTrait([Nio<Trait> trait = NULL], [Nif dominance = NULL])

Sets the mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.)  In the second pattern, dominance is a singleton value; this sets the given dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the dominance for each of the specified traits in one mutation before moving to the next mutation.

@@ -712,11 +708,8 @@

Sets the mutation’s effect(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter effect must follow one of four patterns.  In the first pattern, effect is NULL; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation.  (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.)  In the second pattern, effect is a singleton value; this sets the given effect for each of the specified traits in each target mutation.  In the third pattern, effect is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation.  In the fourth pattern, effect is of length equal to the number of specified traits times the number of target mutations; this uses effect to provide a different effect value for each trait in each mutation, using consecutive values from effect to set the effect for each of the specified traits in one mutation before moving to the next mutation.

– (void)setMutationType(io<MutationType>$ mutType)

-

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  The selection coefficients and dominance coefficients of existing mutations are not changed, since they are properties of the mutation objects themselves; they can be changed explicitly using the setSelectionCoeff() and setDominanceCoeff() methods of Mutation if so desired.

+

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  The effects and dominance coefficients of existing mutations are not changed, since those are properties of the mutation objects themselves; they can be changed explicitly using the setEffectForTrait() and setDominanceForTrait() methods of Mutation if so desired.

In nucleotide-based models, a restriction applies: nucleotide-based mutations may not be changed to a non-nucleotide-based mutation type, and non-nucleotide-based mutations may not be changed to a nucleotide-based mutation type.

-

– (void)setSelectionCoeff(float$ selectionCoeff)

-

Set the selection coefficient of the mutation to selectionCoeff.  The selection coefficient will be changed for all individuals that possess the mutation, since they all share a single Mutation object (note that the dominance coefficient will remain unchanged).

-

Often setting up a mutationEffect() callback is preferable, in order to modify the selection coefficient in a more limited and controlled fashion.  Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

5.11  Class MutationType

5.11.1  MutationType properties

color <–> (string$)

@@ -749,7 +742,7 @@

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.  See also the getValue() and setValue() methods (provided by the Dictionary class; see the Eidos manual), for another way of attaching state to mutation types.

5.11.2  MutationType methods

– (float$)defaultDominanceForTrait([Nio<Trait> trait = NULL])

-

Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their dominanceCoeff property, but that can be changed later with the Mutation method setDominanceCoeff().

+

Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their dominanceCoeff property, but that can be changed later with the Mutation method setDominanceForTrait().

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

– (float)drawEffectForTrait([Nio<Trait> trait = NULL], [integer$ n = 1])

@@ -896,7 +889,7 @@

– (object<SpatialMap>$)divide(ifo<SpatialMap> x)

Divides the spatial map by x.  One possibility is that x is a singleton integer or float value; in this case, each grid value of the target spatial map is divided by x.  Another possibility is that x is an integer or float vector/matrix/array of the same dimensions as the target spatial map’s grid; in this case, each grid value of the target spatial map is divided by the corresponding value of x.  The third possibility is that x is itself a (singleton) spatial map; in this case, each grid value of the target spatial map is divided by the corresponding grid value of x (and thus the two spatial maps must match in their spatiality, their spatial bounds, and their grid dimensions).  The target spatial map is returned, to allow easy chaining of operations.

– (object<SpatialMap>$)exp(void)

-

Exponentiates the values of the spatial map.  More precisely, each grid value x of the target spatial map is exponentiated – replaced by the value ex.  The target spatial map is returned, to allow easy chaining of operations.

+

Exponentiates the values of the spatial map.  More precisely, each grid value x of the target spatial map is exponentiated – replaced by the value ex.  The target spatial map is returned, to allow easy chaining of operations.

– (float)gridValues(void)

Returns the values for the spatial map’s grid as a vector (for a 1D map), a matrix (for a 2D map), or an array (for a 3D map).  The form and orientation of the returned values is such that it could be used to create a new spatial map, with defineSpatialMap(), which would be identical to the original.

– (object<SpatialMap>$)interpolate(integer$ factor, [string$ method = "linear"])

@@ -1029,10 +1022,10 @@

Note that this method is only for use in nonWF models, in which mortality is managed manually by the model script.  In WF models, mortality is managed automatically by the SLiM core when the new offspring generation becomes the parental generation and the previous parental generation dies; mortality does not otherwise occur in WF models.  In nonWF models, mortality normally occurs during the survival stage of the tick cycle, based upon the fitness values calculated by SLiM, and survival() callbacks can influence the outcome of that survival stage.  Calls to killIndividuals(), on the other hand, can be made at any time during first(), early(), or late() events, and the result cannot be modified by survival() callbacks; the given individuals are simply immediately killed.  This method therefore provides an alternative, and relatively rarely used, mortality mechanism that is disconnected from fitness.

– (integer)mutationCounts(Nio<Subpopulation> subpops, [No<Mutation> mutations = NULL])

Return an integer vector with the frequency counts of all of the Mutation objects passed in mutations, within the Subpopulation objects in subpops.  The subpops argument is required, but you may pass NULL to get population-wide frequency counts.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects.  If the optional mutations argument is NULL (the default), frequency counts will be returned for all of the active Mutation objects in the species – the same Mutation objects, and in the same order, as would be returned by the mutations property of sim, in other words.

-

See the -mutationFrequencies() method to obtain float frequencies instead of integer counts.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

+

See the -mutationFrequencies() method to obtain float frequencies instead of integer counts.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

– (float)mutationFrequencies(Nio<Subpopulation> subpops, [No<Mutation> mutations = NULL])

Return a float vector with the frequencies of all of the Mutation objects passed in mutations, within the Subpopulation objects in subpops.  The subpops argument is required, but you may pass NULL to get population-wide frequencies.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects.  If the optional mutations argument is NULL (the default), frequencies will be returned for all of the active Mutation objects in the species – the same Mutation objects, and in the same order, as would be returned by the mutations property of sim, in other words.

-

See the -mutationCounts() method to obtain integer counts instead of float frequencies.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

+

See the -mutationCounts() method to obtain integer counts instead of float frequencies.  See also the Haplosome methods mutationCountsInHaplosomes() and mutationFrequenciesInHaplosomes().

 (object<Mutation>)mutationsOfType(io<MutationType>$ mutType)

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations that are currently active in the species.  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType().  This method is often used to look up an introduced mutation at a later point in the simulation, since there is no way to keep persistent references to objects in SLiM.  This method is provided for speed; it is much faster than the corresponding Eidos code.

– (void)outputFixedMutations([Ns$ filePath = NULL], [logical$ append = F], [logical$ objectTags = F])

@@ -1045,7 +1038,7 @@

Output the state of the entire population.  If the optional parameter filePath is NULL (the default), output will be sent to Eidos’s output stream.  Otherwise, output will be sent to the filesystem path specified by filePath, overwriting that file if append if F, or appending to the end of it if append is T.  When writing to a file, a logical flag, binary, may be supplied as well.  If binary is T, the population state will be written as a binary file instead of a text file (binary data cannot be written to the standard output stream).  The binary file is usually smaller, and in any case will be read much faster than the corresponding text file would be read.  Binary files are not guaranteed to be portable between platforms; in other words, a binary file written on one machine may not be readable on a different machine (but in practice it usually will be, unless the platforms being used are fairly unusual).  If binary is F (the default), a text file will be written.

Beginning with SLiM 2.3, the spatialPositions parameter may be used to control the output of the spatial positions of individuals in species for which continuous space has been enabled using the dimensionality option of initializeSLiMOptions().  If spatialPositions is F, the output will not contain spatial positions, and will be identical to the output generated by SLiM 2.1 and later.  If spatialPositions is T, spatial position information will be output if it is available.  If the species does not have continuous space enabled, the spatialPositions parameter will be ignored.  Positional information may be output for all output destinations – the Eidos output stream, a text file, or a binary file.

Beginning with SLiM 3.0, the ages parameter may be used to control the output of the ages of individuals in nonWF simulations.  If ages is F, the output will not contain ages, preserving backward compatibility with the output format of SLiM 2.1 and later.  If ages is T, ages will be output for nonWF models.  In WF simulations, the ages parameter will be ignored.

-

Beginning with SLiM 3.3, the ancestralNucleotides parameter may be used to control the output of the ancestral nucleotide sequence in nucleotide-based models.  If ancestralNucleotides is F, the output will not contain ancestral nucleotide information, and so the ancestral sequence will not be restored correctly if the saved file is loaded with readPopulationFile().  This option is provided because the ancestral sequence may be quite large, for models with a long chromosome (e.g., 1 GB if the chromosome is 109 bases long, when saved in text format, or 0.25 GB when saved in binary format).  If the model is not nucleotide-based (as enabled with the nucleotideBased parameter to initializeSLiMOptions()), the ancestralNucleotides parameter will be ignored.  Note that in nucleotide-based models the output format will always include the nucleotides associated with any nucleotide-based mutations; the ancestralNucleotides flag governs only the ancestral sequence.

+

Beginning with SLiM 3.3, the ancestralNucleotides parameter may be used to control the output of the ancestral nucleotide sequence in nucleotide-based models.  If ancestralNucleotides is F, the output will not contain ancestral nucleotide information, and so the ancestral sequence will not be restored correctly if the saved file is loaded with readPopulationFile().  This option is provided because the ancestral sequence may be quite large, for models with a long chromosome (e.g., 1 GB if the chromosome is 109 bases long, when saved in text format, or 0.25 GB when saved in binary format).  If the model is not nucleotide-based (as enabled with the nucleotideBased parameter to initializeSLiMOptions()), the ancestralNucleotides parameter will be ignored.  Note that in nucleotide-based models the output format will always include the nucleotides associated with any nucleotide-based mutations; the ancestralNucleotides flag governs only the ancestral sequence.

Beginning with SLiM 3.5, the pedigreeIDs parameter may be used to request that pedigree IDs be written out (and read in by readFromPopulationFile(), subsequently).  This option is turned off (F) by default, for brevity.  This option may only be used if SLiM’s optional pedigree tracking has been enabled with initializeSLiMOptions(keepPedigrees=T).

Beginning with SLiM 5.0, the objectTags parameter may be used to request that tag values for objects be written out.  This option is turned off (F) by default, for brevity; if it turned on (T), the values of all tags for all objects of supported classes (Chromosome, Subpopulation, Individual, Haplosome, Mutation, Substitution) will be written.  For individuals, the tag, tagF, tagL0, tagL1, tagL2, tagL3, and tagL4 properties will be written; for chromosomes, subpopulations, haplosomes, and mutations, the tag property will be written.  The saved tag information can be read in by readFromPopulationFile(), but only if the output is in binary format (binary=T).  Note that if there is other state that you wish you persist, such as tags on objects of other classes, values attached to objects with setValue(), and so forth, you should persist that state in separate files using calls such as writeFile().

Beginning with SLiM 5.0, the substitutions parameter may be used to request that information about Substitution objects in the simulation be written out.  This option is turned off (F) by default, for brevity.  The saved substitution information can be read in by readFromPopulationFile(), but only if the output is in binary format (binary=T).

@@ -1273,7 +1266,7 @@

Returns a vector containing n points that are derived from point by adding a deviation drawn from a dispersal kernel (specified by maxDistance, functionType, and the ellipsis parameters ..., as detailed below) and then applying a boundary condition specified by boundary.  This method therefore performs the steps of a simple dispersal algorithm in a single vectorized call.  See deviatePositions() for an even more efficient approach.

The parameter point may contain a single point which is deviated and bounded n independent times, or may contain n points each of which is deviated and bounded.  In any case, each point in point should match the dimensionality of the model – one element in a 1D model, two elements in a 2D model, or three elements in a 3D model.  This method should not be called in a non-spatial model.

The dispersal kernel is specified similarly to other kernel-based methods, such as setInteractionFunction() and smooth().  For pointDeviated(), functionType may be "f" with no ellipsis arguments ... to use a flat kernel out to maxDistance; "l" with no ellipsis arguments for a kernel that decreases linearly from the center to zero at maxDistance; "e", in which case the ellipsis should supply a numeric$ lambda (rate) parameter for a negative exponential function; "n", in which case the ellipsis should supply a numeric$ sigma (standard deviation) parameter for a Gaussian function; or "t", in which case the ellipsis should supply a numeric$ degrees of freedom and a numeric$ scale parameter for a t-distribution function.  The Cauchy ("c") kernel is not supported by pointDeviated() since it is not well-behaved for this purpose, and the Student’s t ("t") kernel is not allowed in 3D models at present simply because it hasn’t been implemented.  See the InteractionType class documentation for more detailed discussion of the available kernel types and their parameters and probability distribution functions.  For pointDeviated(), the ellipsis parameters that follow functionType may each, independently, be either a singleton or a vector of length equal to n.  This allows each point to be deviated with a different kernel, representing, for example, the movements of individuals with differing dispersal capabilities/propensities.  (However, other parameters such as boundary, maxDistance, and functionType must be the same for all of the points, in the present design.)

-

The random points returned from this method are drawn from the probability distribution that is radially symmetric and has density proportional to the kernel – in other words, at distance r the density is proportional to the kernel type referred to by functionType.  (Said another way, the shape of the cross-section through the probability density function is given by the kernel.)  For instance, the value of the type "e" (exponential) kernel with rate a at r is proportional to exp(−ar), and so in 2D, the probability density that this method with kernel type "e" draws from has density proportional to p(xy) = exp(−a sqrt(x2 + y2)), since r = sqrt(x2 + y2) is the distance.  Note that the distribution of the distance is not given by the kernel except in 1D: in the type "e" example, the distribution of the distance in 1D is exponential, while in 2D it has density proportional to r exp(−ar) (i.e., Gamma with shape parameter 1).  For another example, the value of the type "n" (Normal) kernel at r with standard deviation 1 is proportional to exp(−r2 / 2), and so the density is proportional to p(xy) = exp(−(x2 + y2) / 2).  This is the standard bivariate Normal, and equivalent to drawing independent Normals for the x and y directions; however, the Normal is the only distribution for which independent draws along each axis will result in a radially symmetric distribution.  The distribution of the distance in 2D with type "n" is proportional to r exp(−r2 / 2), i.e., Rayleigh.

+

The random points returned from this method are drawn from the probability distribution that is radially symmetric and has density proportional to the kernel – in other words, at distance r the density is proportional to the kernel type referred to by functionType.  (Said another way, the shape of the cross-section through the probability density function is given by the kernel.)  For instance, the value of the type "e" (exponential) kernel with rate a at r is proportional to exp(−ar), and so in 2D, the probability density that this method with kernel type "e" draws from has density proportional to p(xy) = exp(−a sqrt(x2 + y2)), since r = sqrt(x2 + y2) is the distance.  Note that the distribution of the distance is not given by the kernel except in 1D: in the type "e" example, the distribution of the distance in 1D is exponential, while in 2D it has density proportional to r exp(−ar) (i.e., Gamma with shape parameter 1).  For another example, the value of the type "n" (Normal) kernel at r with standard deviation 1 is proportional to exp(−r2 / 2), and so the density is proportional to p(xy) = exp(−(x2 + y2) / 2).  This is the standard bivariate Normal, and equivalent to drawing independent Normals for the x and y directions; however, the Normal is the only distribution for which independent draws along each axis will result in a radially symmetric distribution.  The distribution of the distance in 2D with type "n" is proportional to r exp(−r2 / 2), i.e., Rayleigh.

The boundary condition must be one of "none", "periodic", "reflecting", "stopping", or "reprising".  For "none", no boundary condition is enforced; the deviated points are simply returned as is.  For "periodic", "reflecting", and "stopping", the boundary condition is enforced just as it is by the pointPeriodic(), pointReflected(), and pointStopped() methods; see their documentation for further details.  For "reprising", if the deviated point is out of bounds a new deviated point will be chosen, based upon the same original point, until a point inside bounds is obtained.  Note that absorbing boundaries (for which being out-of-bounds is lethal) would need to be implemented in script; this method cannot enforce them.  (Note, however, that the deviatePositions() method of Subpopulation can enforce absorbing boundaries.)

Note that for the typical usage case, in which point comes from the spatialPosition property for a vector of individuals, and the result is then set back onto the same vector of individuals using the setSpatialPosition() method, the deviatePositions() method provides an even more efficient alternative.

– (logical)pointInBounds(float point)

@@ -1332,8 +1325,8 @@

5.18.1  Substitution properties

chromosome => (object<Chromosome>$)

The Chromosome object with which the mutation is associated.

-

dominanceCoeff => (float$)

-

The dominance coefficient of the mutation, carried over from the original mutation object.

+

dominance => (float)

+

The dominance coefficient(s) of the mutation, carried over from the original mutation object.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightDominance to access the dominance for that trait.

id => (integer$)

The identifier for this mutation.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run, and that identifier is carried over to the Substitution object when the mutation fixes.

fixationTick => (integer$)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index b7b60a39..b0aa3578 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -5948,27 +5948,23 @@ You can get the \f4\fs20 object with which the mutation is associated.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 dominanceCoeff => (float$)\ +\f3\fs18 \cf2 dominance => (float)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The dominance coefficient of the mutation, taken from the default dominance coefficient of its +\f4\fs20 \cf2 The dominance coefficient(s) of the mutation, taken from the default dominance coefficient(s) of its \f3\fs18 MutationType -\f4\fs20 . If a mutation has a -\f3\fs18 selectionCoeff -\f4\fs20 of -\f1\i s -\f4\i0 and a -\f3\fs18 dominanceCoeff -\f4\fs20 of -\f1\i h -\f4\i0 , the multiplicative fitness effect of the mutation in a homozygote is 1+ -\f1\i s -\f4\i0 , and in a heterozygote is 1+ -\f1\i hs -\f4\i0 . The dominance coefficient of a mutation can be changed with the -\f3\fs18 setDominanceCoeff() +\f4\fs20 . In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 dominanceForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\f3\fs18 Mutation +\f4\fs20 objects will have a dynamic property named +\f3\fs18 heightDominance +\f4\fs20 to access the dominance for that trait. The dominance coefficient(s) of a mutation can be changed with the +\f3\fs18 setDominanceForTrait() \f4\fs20 method.\ -Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s dominance coefficient to some number \f3\fs18 x @@ -5976,11 +5972,34 @@ Note that this property has a quirk: it is stored internally in SLiM using a sin \f3\fs18 mut.dominanceCoeff==x \f4\fs20 may be \f3\fs18 F -\f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly. Instead, it is recommended to use the -\f3\fs18 id -\f4\fs20 or -\f3\fs18 tag -\f4\fs20 properties to identify particular mutations.\ +\f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 effect => (float)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The effect size(s) of the mutation, drawn from the distribution of effect sizes of its +\f3\fs18 MutationType +\f4\fs20 . In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 effectForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\f3\fs18 Mutation +\f4\fs20 objects will have a dynamic property named +\f3\fs18 heightEffect +\f4\fs20 to access the effect for that trait. The effect size of a mutation can be changed with the +\f3\fs18 setEffectForTrait() +\f4\fs20 method.\ +Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +\f3\fs18 mut +\f4\fs20 \'92s effect size to some number +\f3\fs18 x +\f4\fs20 , +\f3\fs18 mut.effect==x +\f4\fs20 may be +\f3\fs18 F +\f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 id => (integer$)\ @@ -6076,42 +6095,6 @@ nucleotide <\'96> (string$)\ \f5 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 selectionCoeff => (float$)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf0 The selection coefficient of the mutation, drawn from the distribution of fitness effects of its -\f3\fs18 MutationType -\f5\fs20 . -\f4 \cf2 \expnd0\expndtw0\kerning0 - If a mutation has a -\f3\fs18 selectionCoeff -\f4\fs20 of -\f1\i s -\f4\i0 , the multiplicative fitness effect of the mutation in a homozygote is 1+ -\f1\i s -\f4\i0 ; in a heterozygote it is 1+ -\f1\i hs -\f4\i0 , where -\f1\i h -\f4\i0 is the dominance coefficient kept by the mutation type. -\f5 \cf0 \kerning1\expnd0\expndtw0 \ - -\f4 Note that this property has a quirk: it is stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation -\f3\fs18 mut -\f4\fs20 \'92s selection coefficient to some number -\f3\fs18 x -\f4\fs20 , -\f3\fs18 mut.selectionCoeff==x -\f4\fs20 may be -\f3\fs18 F -\f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly. Instead, it is recommended to use the -\f3\fs18 id -\f4\fs20 or -\f3\fs18 tag -\f4\fs20 properties to identify particular mutations. -\f5 \ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - \f3\fs18 \cf0 subpopID <\'96> (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6185,21 +6168,6 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(void)setDominanceCoeff(float$\'a0dominanceCoeff)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf2 Set the dominance coefficient of the mutation to -\f3\fs18 dominanceCoeff -\f4\fs20 . The dominance coefficient will be changed for all individuals that possess the mutation, since they all share a single -\f3\fs18 Mutation -\f4\fs20 object (note that the selection coefficient will remain unchanged).\ -Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the -\f3\fs18 Species -\f4\fs20 method -\f3\fs18 recalculateFitness() -\f4\fs20 \'96 but see the documentation of that method for caveats.\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - \f3\fs18 \cf2 +\'a0(void)setDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6271,31 +6239,14 @@ The parameter \f3\fs18 integer \f4\fs20 identifier or a \f3\fs18 MutationType -\f4\fs20 object). The selection coefficients and dominance coefficients of existing mutations are not changed, since they are properties of the mutation objects themselves; they can be changed explicitly using the -\f3\fs18 setSelectionCoeff() +\f4\fs20 object). The effects and dominance coefficients of existing mutations are not changed, since those are properties of the mutation objects themselves; they can be changed explicitly using the +\f3\fs18 setEffectForTrait() \f4\fs20 and -\f3\fs18 setDominanceCoeff() +\f3\fs18 setDominanceForTrait() \f4\fs20 methods of \f3\fs18 Mutation \f4\fs20 if so desired.\ In nucleotide-based models, a restriction applies: nucleotide-based mutations may not be changed to a non-nucleotide-based mutation type, and non-nucleotide-based mutations may not be changed to a nucleotide-based mutation type.\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - -\f3\fs18 \cf2 \'96\'a0(void)setSelectionCoeff(float$\'a0selectionCoeff)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf2 Set the selection coefficient of the mutation to -\f3\fs18 selectionCoeff -\f4\fs20 . The selection coefficient will be changed for all individuals that possess the mutation, since they all share a single -\f3\fs18 Mutation -\f4\fs20 object (note that the dominance coefficient will remain unchanged).\ -Often setting up a -\f3\fs18 mutationEffect() -\f4\fs20 callback is preferable, in order to modify the selection coefficient in a more limited and controlled fashion. Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the -\f3\fs18 Species -\f4\fs20 method -\f3\fs18 recalculateFitness() -\f4\fs20 \'96 but see the documentation of that method for caveats.\ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 \f0\b\fs22 \cf0 5.11 Class MutationType\ @@ -6536,7 +6487,7 @@ The species to which the target object belongs.\ \f4\fs20 property, but that can be changed later with the \f3\fs18 Mutation \f4\fs20 method -\f3\fs18 setDominanceCoeff() +\f3\fs18 setDominanceForTrait() \f4\fs20 .\ Note that dominance coefficients are not bounded. A dominance coefficient greater than \f3\fs18 1.0 @@ -13894,10 +13845,18 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 object with which the mutation is associated.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 dominanceCoeff => (float$)\ +\f3\fs18 \cf2 dominance => (float)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The dominance coefficient of the mutation, carried over from the original mutation object.\ +\f4\fs20 \cf2 The dominance coefficient(s) of the mutation, carried over from the original mutation object. In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 dominanceForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\f3\fs18 Substitution +\f4\fs20 objects will have a dynamic property named +\f3\fs18 heightDominance +\f4\fs20 to access the dominance for that trait.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 id => (integer$)\ diff --git a/VERSIONS b/VERSIONS index c7e472f8..4d15b0bf 100644 --- a/VERSIONS +++ b/VERSIONS @@ -38,8 +38,7 @@ multitrait branch: add Community property allTraits => (object) make a single implicit trait with MakeImplicitTrait() (defaulting to kMultiplicative with a direct fitness effect) if initializeTrait() is not called before initializeMutationType() is called add a C++ dominance_coeff_ property to Mutation, with a value inherited from MutationType's property (which is now just the default value) - add dominanceCoeff properties to Mutation and Substitution - add a setDominanceCoeff() method to Mutation, yay! + add dominance properties to Mutation and Substitution fix calcInbreedingLoad() to use muts.dominanceCoeff instead of muts.mutationType.dominanceCoeff revamp MutationType for multiple traits remove MutationType properties dominanceCoeff, distributionType, and distributionParams properties @@ -64,6 +63,8 @@ multitrait branch: add +setEffectForTrait([Nio traits = NULL], [Nif effect = NULL]) and +setDominanceForTrait([Nio traits = NULL], [Nif dominance = NULL]) methods to Mutation add Effect and Dominance properties to Mutation, both read-write float$ add Effect and Dominance properties to Substitution, both read-only float$ + remove Mutation method setSelectionCoeff(), autofixing to setEffectForTrait(NULL, ) + rename the selectionCoeff property to effect, for both Mutation and Substitution; it changes from float$ to float, and now returns all trait effects; and SLiMgui autofixes this change version 5.1 (Eidos version 4.1): diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 2b8e19bc..3f33b520 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2217,7 +2217,7 @@ const std::vector *Haplosome_Class::Methods(void) cons methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_addMutations, kEidosValueMaskVOID))->AddObject("mutations", gSLiM_Mutation_Class)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_addNewDrawnMutation, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject("mutationType", gSLiM_MutationType_Class)->AddInt("position")->AddIntObject_ON("originSubpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddIntString_ON("nucleotide", gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_addNewMutation, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject("mutationType", gSLiM_MutationType_Class)->AddNumeric("selectionCoeff")->AddInt("position")->AddIntObject_ON("originSubpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddIntString_ON("nucleotide", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_addNewMutation, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject("mutationType", gSLiM_MutationType_Class)->AddNumeric("selectionCoeff")->AddInt("position")->AddIntObject_ON("originSubpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddIntString_ON("nucleotide", gStaticEidosValueNULL)); // FIXME MULTITRAIT methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_containsMarkerMutation, kEidosValueMaskLogical | kEidosValueMaskSingleton | kEidosValueMaskNULL | kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddInt_S("position")->AddLogical_OS("returnMutation", gStaticEidosValue_LogicalF))->DeclareAcceleratedImp(Haplosome::ExecuteMethod_Accelerated_containsMarkerMutation)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_containsMutations, kEidosValueMaskLogical))->AddObject("mutations", gSLiM_Mutation_Class))->DeclareAcceleratedImp(Haplosome::ExecuteMethod_Accelerated_containsMutations)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_countOfMutationsOfType, kEidosValueMaskInt | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Haplosome::ExecuteMethod_Accelerated_countOfMutationsOfType)); diff --git a/core/mutation.cpp b/core/mutation.cpp index 4484e9f9..3987c179 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -263,10 +263,64 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(origin_tick_)); case gID_position: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(position_)); - case gID_selectionCoeff: // ACCELERATED - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selection_coeff_)); - case gID_dominanceCoeff: // ACCELERATED - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance_coeff_)); + case gID_effect: + { + // This is not accelerated, because it's a bit tricky; each mutation could belong to a different species, + // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationIndex mut_index = mutation_block->IndexInBlock(this); + MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + const std::vector &traits = species.Traits(); + size_t trait_count = traits.size(); + + if (trait_count == 1) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selection_coeff_)); // FIXME MULTITRAIT + else if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + slim_effect_t effect = trait_info[trait_index].effect_size_; + + float_result->push_float_no_check(effect); + } + + return EidosValue_SP(float_result); + } + } + case gID_dominance: + { + // This is not accelerated, because it's a bit tricky; each mutation could belong to a different species, + // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationIndex mut_index = mutation_block->IndexInBlock(this); + MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + const std::vector &traits = species.Traits(); + size_t trait_count = traits.size(); + + if (trait_count == 1) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance_coeff_)); // FIXME MULTITRAIT + else if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + slim_effect_t dominance = trait_info[trait_index].dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } + } // variables case gID_nucleotide: // ACCELERATED @@ -495,36 +549,6 @@ EidosValue *Mutation::GetProperty_Accelerated_tag(EidosGlobalStringID p_property return int_result; } -EidosValue *Mutation::GetProperty_Accelerated_selectionCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) -{ -#pragma unused (p_property_id) - EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); - - for (size_t value_index = 0; value_index < p_values_size; ++value_index) - { - Mutation *value = (Mutation *)(p_values[value_index]); - - float_result->set_float_no_check(value->selection_coeff_, value_index); - } - - return float_result; -} - -EidosValue *Mutation::GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) -{ -#pragma unused (p_property_id) - EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); - - for (size_t value_index = 0; value_index < p_values_size; ++value_index) - { - Mutation *value = (Mutation *)(p_values[value_index]); - - float_result->set_float_no_check(value->dominance_coeff_, value_index); - } - - return float_result; -} - EidosValue *Mutation::GetProperty_Accelerated_mutationType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { #pragma unused (p_property_id) @@ -668,8 +692,6 @@ EidosValue_SP Mutation::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, c { case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); - case gID_setSelectionCoeff: return ExecuteMethod_setSelectionCoeff(p_method_id, p_arguments, p_interpreter); - case gID_setDominanceCoeff: return ExecuteMethod_setDominanceCoeff(p_method_id, p_arguments, p_interpreter); case gID_setMutationType: return ExecuteMethod_setMutationType(p_method_id, p_arguments, p_interpreter); default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } @@ -753,69 +775,6 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me } } -// ********************* - (void)setSelectionCoeff(float$ selectionCoeff) -// -EidosValue_SP Mutation::ExecuteMethod_setSelectionCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) -{ -#pragma unused (p_method_id, p_arguments, p_interpreter) - EidosValue *selectionCoeff_value = p_arguments[0].get(); - - double value = selectionCoeff_value->FloatAtIndex_NOCAST(0, nullptr); - slim_effect_t old_coeff = selection_coeff_; - - selection_coeff_ = static_cast(value); - // intentionally no lower or upper bound; -1.0 is lethal, but DFEs may generate smaller values, and we don't want to prevent or bowdlerize that - // also, the dominance coefficient modifies the selection coefficient, so values < -1 are in fact meaningfully different - - // since this selection coefficient came from the user, check and set pure_neutral_ and all_pure_neutral_DFE_ - if (selection_coeff_ != 0.0) - { - Species &species = mutation_type_ptr_->species_; - - species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation - mutation_type_ptr_->all_pure_neutral_DFE_ = false; // let the mutation type for this mutation know that it is no longer pure neutral - - // If a selection coefficient has changed from zero to non-zero, or vice versa, MutationRun's nonneutral mutation caches need revalidation - if (old_coeff == 0.0) - { - species.nonneutral_change_counter_++; - } - } - else if (old_coeff != 0.0) // && (selection_coeff_ == 0.0) implied by the "else" - { - Species &species = mutation_type_ptr_->species_; - - // If a selection coefficient has changed from zero to non-zero, or vice versa, MutationRun's nonneutral mutation caches need revalidation - species.nonneutral_change_counter_++; - } - - // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); - - return gStaticEidosValueVOID; -} - -// ********************* - (void)setDominanceCoeff(float$ dominanceCoeff) -// -EidosValue_SP Mutation::ExecuteMethod_setDominanceCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) -{ -#pragma unused (p_method_id, p_arguments, p_interpreter) - EidosValue *dominanceCoeff_value = p_arguments[0].get(); - - double value = dominanceCoeff_value->FloatAtIndex_NOCAST(0, nullptr); - - dominance_coeff_ = static_cast(value); // intentionally no bounds check - - // cache values used by the fitness calculation code for speed; see header - //cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - //cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); - - return gStaticEidosValueVOID; -} - // ********************* - (void)setMutationType(io$ mutType) // EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -874,8 +833,8 @@ const std::vector *Mutation_Class::Properties(void) properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_nucleotideValue)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_originTick)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_position)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_selectionCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_selectionCoeff)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominanceCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_dominanceCoeff)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effect, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_subpopID)->DeclareAcceleratedSet(Mutation::SetProperty_Accelerated_subpopID)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_tag)->DeclareAcceleratedSet(Mutation::SetProperty_Accelerated_tag)); @@ -899,8 +858,6 @@ const std::vector *Mutation_Class::Methods(void) const methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setSelectionCoeff, kEidosValueMaskVOID))->AddFloat_S("selectionCoeff")); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDominanceCoeff, kEidosValueMaskVOID))->AddFloat_S("dominanceCoeff")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setMutationType, kEidosValueMaskVOID))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); diff --git a/core/mutation.h b/core/mutation.h index f40260cc..58f58b80 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -149,8 +149,6 @@ class Mutation : public EidosDictionaryRetained EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; - EidosValue_SP ExecuteMethod_setSelectionCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_setDominanceCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism @@ -163,8 +161,6 @@ class Mutation : public EidosDictionaryRetained static EidosValue *GetProperty_Accelerated_position(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_subpopID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_selectionCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_mutationType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); // Accelerated property writing; see class EidosObject for comments on this mechanism diff --git a/core/mutation_type.h b/core/mutation_type.h index 7a81706c..5e75cdd8 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -209,7 +209,6 @@ class MutationType : public EidosDictionaryUnretained // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static void SetProperty_Accelerated_convertToSubstitution(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); static void SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); diff --git a/core/slim_functions.cpp b/core/slim_functions.cpp index 031fc1af..b639665b 100644 --- a/core/slim_functions.cpp +++ b/core/slim_functions.cpp @@ -721,7 +721,7 @@ R"V0G0N({ // get h for each mutation; note that this will not work if changing // h using mutationEffect() callbacks or other scripted approaches - h = muts.dominanceCoeff; + h = muts.dominance; // calculate number of haploid lethal equivalents (B or inbreeding load) // this equation is from Morton et al. 1956 diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 9f5b2dca..516afb61 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1249,13 +1249,12 @@ const std::string &gStr_nucleotide = EidosRegisteredString("nucleotide", gID_nuc const std::string &gStr_nucleotideValue = EidosRegisteredString("nucleotideValue", gID_nucleotideValue); const std::string &gStr_originTick = EidosRegisteredString("originTick", gID_originTick); const std::string &gStr_position = EidosRegisteredString("position", gID_position); -const std::string &gStr_selectionCoeff = EidosRegisteredString("selectionCoeff", gID_selectionCoeff); const std::string &gStr_subpopID = EidosRegisteredString("subpopID", gID_subpopID); const std::string &gStr_convertToSubstitution = EidosRegisteredString("convertToSubstitution", gID_convertToSubstitution); const std::string &gStr_defaultDominanceForTrait = EidosRegisteredString("defaultDominanceForTrait", gID_defaultDominanceForTrait); const std::string &gStr_effectDistributionTypeForTrait = EidosRegisteredString("effectDistributionTypeForTrait", gID_effectDistributionTypeForTrait); const std::string &gStr_effectDistributionParamsForTrait = EidosRegisteredString("effectDistributionParamsForTrait", gID_effectDistributionParamsForTrait); -const std::string &gStr_dominanceCoeff = EidosRegisteredString("dominanceCoeff", gID_dominanceCoeff); +const std::string &gStr_dominance = EidosRegisteredString("dominance", gID_dominance); const std::string &gStr_hemizygousDominanceCoeff = EidosRegisteredString("hemizygousDominanceCoeff", gID_hemizygousDominanceCoeff); const std::string &gStr_mutationStackGroup = EidosRegisteredString("mutationStackGroup", gID_mutationStackGroup); const std::string &gStr_mutationStackPolicy = EidosRegisteredString("mutationStackPolicy", gID_mutationStackPolicy); @@ -1381,8 +1380,6 @@ const std::string &gStr_effectForTrait = EidosRegisteredString("effectForTrait", const std::string &gStr_dominanceForTrait = EidosRegisteredString("dominanceForTrait", gID_dominanceForTrait); const std::string &gStr_setEffectForTrait = EidosRegisteredString("setEffectForTrait", gID_setEffectForTrait); const std::string &gStr_setDominanceForTrait = EidosRegisteredString("setDominanceForTrait", gID_setDominanceForTrait); -const std::string &gStr_setSelectionCoeff = EidosRegisteredString("setSelectionCoeff", gID_setSelectionCoeff); -const std::string &gStr_setDominanceCoeff = EidosRegisteredString("setDominanceCoeff", gID_setDominanceCoeff); const std::string &gStr_setMutationType = EidosRegisteredString("setMutationType", gID_setMutationType); const std::string &gStr_drawEffectForTrait = EidosRegisteredString("drawEffectForTrait", gID_drawEffectForTrait); const std::string &gStr_setDefaultDominanceForTrait = EidosRegisteredString("setDefaultDominanceForTrait", gID_setDefaultDominanceForTrait); diff --git a/core/slim_globals.h b/core/slim_globals.h index 5d4365f4..412f67b3 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -840,13 +840,12 @@ extern const std::string &gStr_nucleotide; extern const std::string &gStr_nucleotideValue; extern const std::string &gStr_originTick; extern const std::string &gStr_position; -extern const std::string &gStr_selectionCoeff; extern const std::string &gStr_subpopID; extern const std::string &gStr_convertToSubstitution; extern const std::string &gStr_defaultDominanceForTrait; extern const std::string &gStr_effectDistributionTypeForTrait; extern const std::string &gStr_effectDistributionParamsForTrait; -extern const std::string &gStr_dominanceCoeff; +extern const std::string &gStr_dominance; extern const std::string &gStr_hemizygousDominanceCoeff; extern const std::string &gStr_mutationStackGroup; extern const std::string &gStr_mutationStackPolicy; @@ -971,8 +970,6 @@ extern const std::string &gStr_effectForTrait; extern const std::string &gStr_dominanceForTrait; extern const std::string &gStr_setEffectForTrait; extern const std::string &gStr_setDominanceForTrait; -extern const std::string &gStr_setSelectionCoeff; -extern const std::string &gStr_setDominanceCoeff; extern const std::string &gStr_setMutationType; extern const std::string &gStr_drawEffectForTrait; extern const std::string &gStr_setDefaultDominanceForTrait; @@ -1321,13 +1318,12 @@ enum _SLiMGlobalStringID : int { gID_nucleotideValue, gID_originTick, gID_position, - gID_selectionCoeff, gID_subpopID, gID_convertToSubstitution, gID_defaultDominanceForTrait, gID_effectDistributionTypeForTrait, gID_effectDistributionParamsForTrait, - gID_dominanceCoeff, + gID_dominance, gID_hemizygousDominanceCoeff, gID_mutationStackGroup, gID_mutationStackPolicy, @@ -1452,8 +1448,6 @@ enum _SLiMGlobalStringID : int { gID_dominanceForTrait, gID_setEffectForTrait, gID_setDominanceForTrait, - gID_setSelectionCoeff, - gID_setDominanceCoeff, gID_setMutationType, gID_drawEffectForTrait, gID_setDefaultDominanceForTrait, diff --git a/core/slim_test.cpp b/core/slim_test.cpp index f12f7e7d..1f6270b8 100644 --- a/core/slim_test.cpp +++ b/core/slim_test.cpp @@ -361,7 +361,7 @@ std::string gen1_setup("initialize() { initializeMutationRate(1e-7); initializeM std::string gen1_setup_sex("initialize() { initializeSex('X'); initializeMutationRate(1e-7); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } "); std::string gen2_stop(" 2 early() { stop(); } "); std::string gen1_setup_highmut_p1("initialize() { initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } "); -std::string gen1_setup_fixmut_p1("initialize() { initializeMutationRate(1e-4); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } 10 early() { sim.mutations[0].setSelectionCoeff(500.0); sim.recalculateFitness(); } "); +std::string gen1_setup_fixmut_p1("initialize() { initializeMutationRate(1e-4); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } 10 early() { sim.mutations[0].setEffectForTrait(0, 500.0); sim.recalculateFitness(); } "); std::string gen1_setup_i1("initialize() { initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeInteractionType('i1', ''); } 1 early() { sim.addSubpop('p1', 10); } 1:10 late() { } "); std::string gen1_setup_i1x("initialize() { initializeSLiMOptions(dimensionality='x'); initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeInteractionType('i1', 'x'); } 1 early() { sim.addSubpop('p1', 10); } 1:10 late() { p1.individuals.x = runif(10); } "); std::string gen1_setup_i1xPx("initialize() { initializeSLiMOptions(dimensionality='x', periodicity='x'); initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeInteractionType('i1', 'x'); } 1 early() { sim.addSubpop('p1', 10); } 1:10 late() { p1.individuals.x = runif(10); } "); diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index f49ad1b8..03c720d4 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -652,12 +652,12 @@ void _RunMutationTests(void) SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if (mut.mutationType == m1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if ((mut.originTick >= 1) & (mut.originTick < 10)) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if ((mut.position >= 0) & (mut.position < 100000)) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if (mut.selectionCoeff == 0.0) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if (mut.effect == 0.0) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if (mut.subpopID == 1) stop(); }", __LINE__); SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.mutationType = m1; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.originTick = 1; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.position = 0; stop(); }", "read-only property", __LINE__); - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.selectionCoeff = 0.1; stop(); }", "read-only property", __LINE__); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.effect = 0.1; stop(); }", "read-only property", __LINE__); SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.subpopID = 237; if (mut.subpopID == 237) stop(); }", __LINE__); // legal; this field may be used as a user tag SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.tag; }", "before being set", __LINE__); SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; c(mut,mut).tag; }", "before being set", __LINE__); @@ -667,12 +667,6 @@ void _RunMutationTests(void) SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.setMutationType(m1); if (mut.mutationType == m1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.setMutationType(m1); if (mut.mutationType == m1) stop(); }", __LINE__); SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.setMutationType(2); if (mut.mutationType == m1) stop(); }", "mutation type m2 not defined", __LINE__); - - // Test Mutation - (void)setSelectionCoeff(float$ selectionCoeff) - SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.setSelectionCoeff(0.5); if (mut.selectionCoeff == 0.5) stop(); }", __LINE__); - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.setSelectionCoeff(1); if (mut.selectionCoeff == 1) stop(); }", "cannot be type integer", __LINE__); - SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.setSelectionCoeff(-500.0); if (mut.selectionCoeff == -500.0) stop(); }", __LINE__); // legal; no lower bound - SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.setSelectionCoeff(500.0); if (mut.selectionCoeff == 500.0) stop(); }", __LINE__); // legal; no upper bound } #pragma mark Substitution tests diff --git a/core/slim_test_other.cpp b/core/slim_test_other.cpp index b244401a..c082ee3c 100644 --- a/core/slim_test_other.cpp +++ b/core/slim_test_other.cpp @@ -2690,7 +2690,7 @@ void _RunNucleotideMethodTests(void) // nucleotide & nucleotideValue std::string nuc_highmut("initialize() { initializeSLiMOptions(nucleotideBased=T); initializeAncestralNucleotides(randomNucleotides(1e2)); initializeMutationTypeNuc('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0, mmJukesCantor(1e-2)); initializeGenomicElement(g1, 0, 1e2-1); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } "); - std::string nuc_fixmut("initialize() { initializeSLiMOptions(nucleotideBased=T); initializeAncestralNucleotides(randomNucleotides(1e2)); initializeMutationTypeNuc('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0, mmJukesCantor(1e-2)); initializeGenomicElement(g1, 0, 1e2-1); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } 10 early() { sim.mutations[0].setSelectionCoeff(500.0); sim.recalculateFitness(); } "); + std::string nuc_fixmut("initialize() { initializeSLiMOptions(nucleotideBased=T); initializeAncestralNucleotides(randomNucleotides(1e2)); initializeMutationTypeNuc('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0, mmJukesCantor(1e-2)); initializeGenomicElement(g1, 0, 1e2-1); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } 10 early() { sim.mutations[0].setEffectForTrait(0, 500.0); sim.recalculateFitness(); } "); SLiMAssertScriptStop(nuc_highmut + "10 early() { mut = sim.mutations[0]; mut.nucleotide; stop(); }", __LINE__); SLiMAssertScriptStop(nuc_highmut + "10 early() { mut = sim.mutations[0]; mut.nucleotideValue; stop(); }", __LINE__); diff --git a/core/substitution.cpp b/core/substitution.cpp index 647d6b49..6eaa8b4a 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -173,13 +173,61 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) return mutation_type_ptr_->SymbolTableEntry().second; case gID_position: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(position_)); - case gID_selectionCoeff: // ACCELERATED - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selection_coeff_)); - case gID_dominanceCoeff: // ACCELERATED - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance_coeff_)); + case gID_effect: + { + // This is not accelerated, because it's a bit tricky; each substitution could belong to a different species, + // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + size_t trait_count = traits.size(); + + if (trait_count == 1) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selection_coeff_)); // FIXME MULTITRAIT + else if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + slim_effect_t effect = trait_info_[trait_index].effect_size_; + + float_result->push_float_no_check(effect); + } + + return EidosValue_SP(float_result); + } + } + case gID_dominance: + { + // This is not accelerated, because it's a bit tricky; each substitution could belong to a different species, + // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + size_t trait_count = traits.size(); + + if (trait_count == 1) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance_coeff_)); // FIXME MULTITRAIT + else if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } + } case gID_originTick: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(origin_tick_)); - case gID_fixationTick: // ACCELERATED + case gID_fixationTick: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(fixation_tick_)); // variables @@ -391,36 +439,6 @@ EidosValue *Substitution::GetProperty_Accelerated_tag(EidosGlobalStringID p_prop return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_selectionCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) -{ -#pragma unused (p_property_id) - EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); - - for (size_t value_index = 0; value_index < p_values_size; ++value_index) - { - Substitution *value = (Substitution *)(p_values[value_index]); - - float_result->set_float_no_check(value->selection_coeff_, value_index); - } - - return float_result; -} - -EidosValue *Substitution::GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) -{ -#pragma unused (p_property_id) - EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); - - for (size_t value_index = 0; value_index < p_values_size; ++value_index) - { - Substitution *value = (Substitution *)(p_values[value_index]); - - float_result->set_float_no_check(value->dominance_coeff_, value_index); - } - - return float_result; -} - EidosValue *Substitution::GetProperty_Accelerated_mutationType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { #pragma unused (p_property_id) @@ -592,8 +610,8 @@ const std::vector *Substitution_Class::Properties(vo properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_mutationType)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_position)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_selectionCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_selectionCoeff)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominanceCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_dominanceCoeff)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effect, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_subpopID)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotide, false, kEidosValueMaskString | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotide)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotideValue)); diff --git a/core/substitution.h b/core/substitution.h index 1b8fe141..5bbf6f6b 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -107,8 +107,6 @@ class Substitution : public EidosDictionaryRetained static EidosValue *GetProperty_Accelerated_position(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_subpopID(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_selectionCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_dominanceCoeff(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_mutationType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); }; From 6c1a4f97688269545ccdf21f063aeabe9d2aab0d Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 25 Oct 2025 15:01:36 -0400 Subject: [PATCH 023/107] remove C++ selection_coeff_ and dominance_coeff_ ivars --- QtSLiM/QtSLiMChromosomeWidget_GL.cpp | 33 +- QtSLiM/QtSLiMChromosomeWidget_QT.cpp | 32 +- QtSLiM/QtSLiMHaplotypeManager.cpp | 11 +- QtSLiM/help/SLiMHelpClasses.html | 4 +- SLiMgui/ChromosomeView.mm | 37 +- SLiMgui/SLiMHelpClasses.rtf | 22 +- VERSIONS | 1 + core/chromosome.cpp | 17 +- core/haplosome.cpp | 110 +++-- core/individual.cpp | 20 +- core/mutation.cpp | 601 ++++++++++++++++++++------- core/mutation.h | 39 +- core/mutation_block.h | 7 +- core/mutation_run.cpp | 17 +- core/mutation_type.cpp | 62 ++- core/mutation_type.h | 10 +- core/polymorphism.cpp | 111 ++++- core/population.cpp | 36 +- core/population.h | 3 - core/slim_functions.cpp | 7 +- core/slim_test_genetics.cpp | 8 +- core/species.cpp | 19 +- core/species.h | 3 - core/subpopulation.cpp | 69 +-- core/substitution.cpp | 37 +- core/substitution.h | 2 - 26 files changed, 947 insertions(+), 371 deletions(-) diff --git a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp index 8dd3a106..e9cbc6bc 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp @@ -177,10 +177,12 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch static std::vector mutations; mutations.resize(0); + MutationBlock *mutation_block = displaySpecies->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + { int registry_size; const MutationIndex *registry = pop.MutationRegistry(®istry_size); - Mutation *mut_block_ptr = displaySpecies->SpeciesMutationBlock()->mutation_buffer_; slim_chromosome_index_t chromosome_index = chromosome->Index(); for (int registry_index = 0; registry_index < registry_size; ++registry_index) @@ -220,7 +222,10 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch } else { - RGBForSelectionCoeff(static_cast(mutation->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + + RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } int height_adjust = mutationTickRect.height() - static_cast(ceil((mutationRefCount / totalHaplosomeCount) * interiorRect.height())); @@ -281,7 +286,10 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfloat-equal" // We do want to do an exact floating-point equality compare here; we want to see whether the mutation's selcoeff is unmodified from the fixed DFE - if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mutation->selection_coeff_ == mut_type_selcoeff))) + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + + if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mut_trait_info[0].effect_size_ == mut_type_selcoeff))) #pragma clang diagnostic pop #pragma GCC diagnostic pop { @@ -369,7 +377,11 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch int height_adjust = mutationTickRect.height() - static_cast(ceil((mutationRefCount / totalHaplosomeCount) * interiorRect.height())); mutationTickRect.setTop(mutationTickRect.top() + height_adjust); - RGBForSelectionCoeff(static_cast(mutation->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + + RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -421,8 +433,11 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch mutationTickRect.setTop(mutationTickRect.top() + interiorRect.height() - barHeight); const Mutation *mutation = mutationBuffer[binIndex]; - - RGBForSelectionCoeff(static_cast(mutation->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + + RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -489,7 +504,8 @@ void QtSLiMChromosomeWidget::glDrawFixedSubstitutions(QRect &interiorRect, Chrom } else { - RGBForSelectionCoeff(static_cast(substitution->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + RGBForSelectionCoeff(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } } @@ -568,7 +584,8 @@ void QtSLiMChromosomeWidget::glDrawFixedSubstitutions(QRect &interiorRect, Chrom } else { - RGBForSelectionCoeff(static_cast(substitution->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + RGBForSelectionCoeff(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } mutationTickRect.setX(interiorRect.x() + binIndex); diff --git a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp index a1349368..78009f96 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp @@ -173,10 +173,12 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch static std::vector mutations; mutations.resize(0); + MutationBlock *mutation_block = displaySpecies->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + { int registry_size; const MutationIndex *registry = pop.MutationRegistry(®istry_size); - Mutation *mut_block_ptr = displaySpecies->SpeciesMutationBlock()->mutation_buffer_; slim_chromosome_index_t chromosome_index = chromosome->Index(); for (int registry_index = 0; registry_index < registry_size; ++registry_index) @@ -216,7 +218,10 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch } else { - RGBForSelectionCoeff(static_cast(mutation->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + + RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } int height_adjust = mutationTickRect.height() - static_cast(ceil((mutationRefCount / totalHaplosomeCount) * interiorRect.height())); @@ -277,7 +282,10 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfloat-equal" // We do want to do an exact floating-point equality compare here; we want to see whether the mutation's selcoeff is unmodified from the fixed DFE - if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mutation->selection_coeff_ == mut_type_selcoeff))) + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + + if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mut_trait_info[0].effect_size_ == mut_type_selcoeff))) #pragma clang diagnostic pop #pragma GCC diagnostic pop { @@ -365,7 +373,11 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch int height_adjust = mutationTickRect.height() - static_cast(ceil((mutationRefCount / totalHaplosomeCount) * interiorRect.height())); mutationTickRect.setTop(mutationTickRect.top() + height_adjust); - RGBForSelectionCoeff(static_cast(mutation->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + + RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -417,8 +429,11 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch mutationTickRect.setTop(mutationTickRect.top() + interiorRect.height() - barHeight); const Mutation *mutation = mutationBuffer[binIndex]; - - RGBForSelectionCoeff(static_cast(mutation->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + + RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -485,7 +500,8 @@ void QtSLiMChromosomeWidget::qtDrawFixedSubstitutions(QRect &interiorRect, Chrom } else { - RGBForSelectionCoeff(static_cast(substitution->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + RGBForSelectionCoeff(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } } @@ -564,7 +580,7 @@ void QtSLiMChromosomeWidget::qtDrawFixedSubstitutions(QRect &interiorRect, Chrom } else { - RGBForSelectionCoeff(static_cast(substitution->selection_coeff_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForSelectionCoeff(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } mutationTickRect.setX(interiorRect.x() + binIndex); diff --git a/QtSLiM/QtSLiMHaplotypeManager.cpp b/QtSLiM/QtSLiMHaplotypeManager.cpp index eeed3b14..22dee088 100644 --- a/QtSLiM/QtSLiMHaplotypeManager.cpp +++ b/QtSLiM/QtSLiMHaplotypeManager.cpp @@ -481,7 +481,8 @@ void QtSLiMHaplotypeManager::configureMutationInfoBuffer(Chromosome *chromosome) mutationPositions = static_cast(malloc(sizeof(slim_position_t) * mutationIndexCount)); // Copy the information we need on each mutation in use - Mutation *mut_block_ptr = graphSpecies->SpeciesMutationBlock()->mutation_buffer_; + MutationBlock *mutation_block = graphSpecies->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; for (const MutationIndex *reg_ptr = registry; reg_ptr != reg_end_ptr; ++reg_ptr) { @@ -493,6 +494,10 @@ void QtSLiMHaplotypeManager::configureMutationInfoBuffer(Chromosome *chromosome) haplo_mut->position_ = mut_position; *(mutationPositions + mut_index) = mut_position; + + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + slim_effect_t selection_coeff = mut_trait_info[0].effect_size_; if (!mut_type->color_.empty()) { @@ -502,10 +507,10 @@ void QtSLiMHaplotypeManager::configureMutationInfoBuffer(Chromosome *chromosome) } else { - RGBForSelectionCoeff(static_cast(mut->selection_coeff_), &haplo_mut->red_, &haplo_mut->green_, &haplo_mut->blue_, scalingFactor); + RGBForSelectionCoeff(static_cast(selection_coeff), &haplo_mut->red_, &haplo_mut->green_, &haplo_mut->blue_, scalingFactor); } - haplo_mut->neutral_ = (mut->selection_coeff_ == 0.0f); + haplo_mut->neutral_ = (selection_coeff == 0.0f); haplo_mut->display_ = mut_type->mutation_type_displayed_; } diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 6aca7c1d..fe3af8c7 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -1327,6 +1327,8 @@

The Chromosome object with which the mutation is associated.

dominance => (float)

The dominance coefficient(s) of the mutation, carried over from the original mutation object.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightDominance to access the dominance for that trait.

+

effect => (float)

+

The selection coefficient(s) of the mutation, carried over from the original mutation object.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightEffect to access the effect for that trait.

id => (integer$)

The identifier for this mutation.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run, and that identifier is carried over to the Substitution object when the mutation fixes.

fixationTick => (integer$)

@@ -1341,8 +1343,6 @@

The tick in which this mutation arose.

position => (integer$)

The position in the chromosome of this mutation.

-

selectionCoeff => (float$)

-

The selection coefficient of the mutation, drawn from the distribution of fitness effects of its MutationType.

subpopID <–> (integer$)

The identifier of the subpopulation in which this mutation arose.  This value is carried over from the Mutation object directly; if a “tag” value was used in the Mutation object, that value will carry over to the corresponding Substitution object.  The subpopID in Substitution is a read-write property to allow it to be used as a “tag” in the same way, if the origin subpopulation identifier is not needed.

tag <–> (integer$)

diff --git a/SLiMgui/ChromosomeView.mm b/SLiMgui/ChromosomeView.mm index cc0f5d2c..579600f0 100644 --- a/SLiMgui/ChromosomeView.mm +++ b/SLiMgui/ChromosomeView.mm @@ -512,7 +512,8 @@ - (void)drawFixedSubstitutionsInInteriorRect:(NSRect)interiorRect chromosome:(Ch } else { - RGBForSelectionCoeff(substitution->selection_coeff_, &colorRed, &colorGreen, &colorBlue, scalingFactor); + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + RGBForSelectionCoeff(substitution->trait_info_[0].effect_size_, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; } } @@ -584,7 +585,8 @@ - (void)drawFixedSubstitutionsInInteriorRect:(NSRect)interiorRect chromosome:(Ch } else { - RGBForSelectionCoeff(substitution->selection_coeff_, &colorRed, &colorGreen, &colorBlue, scalingFactor); + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + RGBForSelectionCoeff(substitution->trait_info_[0].effect_size_, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; } @@ -640,14 +642,16 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome double totalHaplosomeCount = chromosome->gui_total_haplosome_count_; // this includes only haplosomes in the selected subpopulations int registry_size; const MutationIndex *registry = pop.MutationRegistry(®istry_size); - Mutation *mut_block_ptr = displaySpecies->SpeciesMutationBlock()->mutation_buffer_; + MutationBlock *mutation_block = displaySpecies->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; if ((registry_size < 1000) || (displayedRange.length < interiorRect.size.width)) { // This is the simple version of the display code, avoiding the memory allocations and such for (int registry_index = 0; registry_index < registry_size; ++registry_index) { - const Mutation *mutation = mut_block_ptr + registry[registry_index]; + MutationIndex mut_index = registry[registry_index]; + const Mutation *mutation = mut_block_ptr + mut_index; if (mutation->chromosome_index_ == chromosome_index) // display only mutations in the displayed chromosome { @@ -665,8 +669,11 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome } else { + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + slim_effect_t mut_effect = mutation_block->TraitInfoForIndex(mut_index)[0].effect_size_; float colorRed = 0.0, colorGreen = 0.0, colorBlue = 0.0; - RGBForSelectionCoeff(mutation->selection_coeff_, &colorRed, &colorGreen, &colorBlue, scalingFactor); + + RGBForSelectionCoeff(mut_effect, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; } @@ -714,9 +721,11 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome // Scan through the mutation list for mutations of this type with the right selcoeff for (int registry_index = 0; registry_index < registry_size; ++registry_index) { - const Mutation *mutation = mut_block_ptr + registry[registry_index]; + MutationIndex mut_index = registry[registry_index]; + const Mutation *mutation = mut_block_ptr + mut_index; - if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mutation->selection_coeff_ == mut_type_selcoeff))) + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mutation_block->TraitInfoForIndex(mut_index)[0].effect_size_ == mut_type_selcoeff))) { if (mutation->chromosome_index_ == chromosome_index) { @@ -792,16 +801,19 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome //if (mutation->gui_scratch_reference_count_ == 0) if (!mutationsPlotted[registry_index]) { - const Mutation *mutation = mut_block_ptr + registry[registry_index]; + MutationIndex mut_index = registry[registry_index]; + const Mutation *mutation = mut_block_ptr + mut_index; if (mutation->chromosome_index_ == chromosome_index) { + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + slim_effect_t mut_effect = mutation_block->TraitInfoForIndex(mut_index)[0].effect_size_; slim_refcount_t mutationRefCount = mutation->gui_reference_count_; // this includes only references made from the selected subpopulations slim_position_t mutationPosition = mutation->position_; NSRect mutationTickRect = [self rectEncompassingBase:mutationPosition toBase:mutationPosition interiorRect:interiorRect displayedRange:displayedRange]; mutationTickRect.size.height = (int)ceil((mutationRefCount / totalHaplosomeCount) * interiorRect.size.height); - RGBForSelectionCoeff(mutation->selection_coeff_, &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForSelectionCoeff(mut_effect, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; NSRectFill(mutationTickRect); } @@ -853,9 +865,12 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome if (height) { NSRect mutationTickRect = NSMakeRect(interiorRect.origin.x + binIndex, interiorRect.origin.y, 1, height); - const Mutation *mutation = mut_block_ptr + mutationBuffer[binIndex]; + MutationIndex mut_index = mutationBuffer[binIndex]; + + // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! + slim_effect_t mut_effect = mutation_block->TraitInfoForIndex(mut_index)[0].effect_size_; - RGBForSelectionCoeff(mutation->selection_coeff_, &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForSelectionCoeff(mut_effect, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; NSRectFill(mutationTickRect); } diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index b0aa3578..828af7d7 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -13859,6 +13859,20 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 to access the dominance for that trait.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 effect => (float)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The selection coefficient(s) of the mutation, carried over from the original mutation object. In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 effectForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\f3\fs18 Substitution +\f4\fs20 objects will have a dynamic property named +\f3\fs18 heightEffect +\f4\fs20 to access the effect for that trait.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf0 id => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -13931,14 +13945,6 @@ nucleotide <\'96> (string$)\ \f5 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 selectionCoeff => (float$)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf0 The selection coefficient of the mutation, drawn from the distribution of fitness effects of its -\f3\fs18 MutationType -\f5\fs20 .\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - \f3\fs18 \cf0 subpopID <\'96> (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 diff --git a/VERSIONS b/VERSIONS index 4d15b0bf..c8cb25be 100644 --- a/VERSIONS +++ b/VERSIONS @@ -65,6 +65,7 @@ multitrait branch: add Effect and Dominance properties to Substitution, both read-only float$ remove Mutation method setSelectionCoeff(), autofixing to setEffectForTrait(NULL, ) rename the selectionCoeff property to effect, for both Mutation and Substitution; it changes from float$ to float, and now returns all trait effects; and SLiMgui autofixes this change + remove the old C++ selection_coeff_ and dominance_coeff_ ivars in Mutation and Substitution, and begin the transition over to the new MutationTraitInfo struct version 5.1 (Eidos version 4.1): diff --git a/core/chromosome.cpp b/core/chromosome.cpp index ed6b323b..27cc2f1a 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1036,8 +1036,6 @@ MutationIndex Chromosome::DrawNewMutation(std::pair(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT - // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE, SINCE WE DO NOT KNOW WHAT HAPLOSOME WE WILL BE INSERTED INTO! THIS IS THE CALLER'S RESPONSIBILITY! MutationBlock *mutation_block = mutation_block_; MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); @@ -1045,7 +1043,7 @@ MutationIndex Chromosome::DrawNewMutation(std::pairmutation_buffer_ + new_mut_index; - new (mutation) Mutation(mutation_type_ptr, index_, p_position.first, selection_coeff, mutation_type_ptr->DefaultDominanceForTrait(0), p_subpop_index, p_tick, -1); // FIXME MULTITRAIT + new (mutation) Mutation(mutation_type_ptr, index_, p_position.first, p_subpop_index, p_tick, -1); // addition to the main registry and the muttype registries will happen if the new mutation clears the stacking policy @@ -1407,14 +1405,12 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pair(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT - // NOTE THAT THE STACKING POLICY IS NOT ENFORCED HERE! THIS IS THE CALLER'S RESPONSIBILITY! MutationBlock *mutation_block = mutation_block_; MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); Mutation *mutation = mutation_block->mutation_buffer_ + new_mut_index; - new (mutation) Mutation(mutation_type_ptr, index_, position, selection_coeff, mutation_type_ptr->DefaultDominanceForTrait(0), p_subpop_index, p_tick, nucleotide); // FIXME MULTITRAIT + new (mutation) Mutation(mutation_type_ptr, index_, position, p_subpop_index, p_tick, nucleotide); // Call mutation() callbacks if there are any if (p_mutation_callbacks) @@ -1439,11 +1435,10 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pairIndexInBlock(post_callback_mut); - if (new_mut_index != post_callback_mut_index) - { - //std::cout << "replacing mutation!" << std::endl; - new_mut_index = post_callback_mut_index; - } + //if (new_mut_index != post_callback_mut_index) + // std::cout << "replacing mutation!" << std::endl; + + new_mut_index = post_callback_mut_index; } // addition to the main registry and the muttype registries will happen if the new mutation clears the stacking policy diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 3f33b520..30b492ae 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -1291,6 +1291,8 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *mutType_value = p_arguments[0].get(); + // FIXME MULTITRAIT: This should perhaps take a trait as its second parameter, so that it can be used with any trait; and its doc needs to be rewritten; and it should be deprecated + if (IsDeferred()) EIDOS_TERMINATION << "ERROR (Haplosome::ExecuteMethod_sumOfMutationsOfType): the mutations of deferred haplosomes cannot be accessed." << EidosTerminate(); if (IsNull()) @@ -1300,7 +1302,8 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species.community_, &species, "sumOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK // Sum the selection coefficients of mutations of the given type - Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; double selcoeff_sum = 0.0; int mutrun_count = mutrun_count_; @@ -1310,12 +1313,16 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID int haplosome1_count = mutrun->size(); const MutationIndex *haplosome_ptr = mutrun->begin_pointer_const(); - for (int mut_index = 0; mut_index < haplosome1_count; ++mut_index) + for (int index_in_mutrun = 0; index_in_mutrun < haplosome1_count; ++index_in_mutrun) { - Mutation *mut_ptr = mut_block_ptr + haplosome_ptr[mut_index]; + MutationIndex mut_index = haplosome_ptr[index_in_mutrun]; + Mutation *mut_ptr = mut_block_ptr + mut_index; if (mut_ptr->mutation_type_ptr_ == mutation_type_ptr) - selcoeff_sum += mut_ptr->selection_coeff_; + { + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mut_index); + selcoeff_sum += mut_trait_info[0].effect_size_; + } } } @@ -1687,7 +1694,8 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i Species &species = p_chromosome.species_; bool nucleotide_based = species.IsNucleotideBased(); NucleotideArray *ancestral_seq = p_chromosome.AncestralSequence(); - Mutation *mut_block_ptr = species.SpeciesMutationBlock()->mutation_buffer_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; int64_t individual_count; // if groupAsIndividuals is false, we just act as though the chromosome is haploid @@ -1952,7 +1960,12 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i { if (polymorphism != nuc_based.front()) p_out << ','; - p_out << polymorphism->mutation_ptr_->selection_coeff_; + + // FIXME MULTITRAIT: need to write out all trait info, not just trait 0 + const Mutation *mut = polymorphism->mutation_ptr_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + p_out << mut_trait_info[0].effect_size_; } p_out << ";"; @@ -1961,7 +1974,12 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i { if (polymorphism != nuc_based.front()) p_out << ','; - p_out << polymorphism->mutation_ptr_->dominance_coeff_; + + // FIXME MULTITRAIT: need to write out all trait info, not just trait 0 + const Mutation *mut = polymorphism->mutation_ptr_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + p_out << mut_trait_info[0].dominance_coeff_; } p_out << ";"; @@ -2084,9 +2102,12 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i p_out << "\t1000\tPASS\t"; // emit the INFO fields and the Genotype marker + // FIXME MULTITRAIT: need to write out all trait info, not just trait 0 + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); + p_out << "MID=" << mutation->mutation_id_ << ";"; - p_out << "S=" << mutation->selection_coeff_ << ";"; - p_out << "DOM=" << mutation->dominance_coeff_ << ";"; + p_out << "S=" << mut_trait_info->effect_size_ << ";"; + p_out << "DOM=" << mut_trait_info->dominance_coeff_ << ";"; p_out << "PO=" << mutation->subpop_index_ << ";"; p_out << "TO=" << mutation->origin_tick_ << ";"; p_out << "MT=" << mutation->mutation_type_ptr_->mutation_type_id_ << ";"; @@ -2809,7 +2830,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // for the singleton case for each of the parameters, get all the info MutationType *singleton_mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(arg_muttype, 0, &community, species, method_name.c_str()); // SPECIES CONSISTENCY CHECK - double singleton_selection_coeff = (arg_selcoeff ? arg_selcoeff->NumericAtIndex_NOCAST(0, nullptr) : 0.0); + slim_effect_t singleton_selection_coeff = (arg_selcoeff ? (slim_effect_t)arg_selcoeff->NumericAtIndex_NOCAST(0, nullptr) : 0.0); slim_position_t singleton_position = SLiMCastToPositionTypeOrRaise(arg_position->IntAtIndex_NOCAST(0, nullptr)); @@ -2864,7 +2885,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // It is possible that some mutations will not actually be added to any haplosome, due to stacking; they will be cleared from the // registry as lost mutations in the next cycle. All mutations are returned to the user, whether actually added or not. MutationType *mutation_type_ptr = singleton_mutation_type_ptr; - double selection_coeff = singleton_selection_coeff; slim_position_t position = singleton_position; slim_objectid_t origin_subpop_id = singleton_origin_subpop_id; int64_t nucleotide = singleton_nucleotide; @@ -2880,14 +2900,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID if (muttype_count != 1) mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(arg_muttype, mut_parameter_index, &community, species, method_name.c_str()); // SPECIES CONSISTENCY CHECK - if (selcoeff_count != 1) - { - if (arg_selcoeff) - selection_coeff = arg_selcoeff->NumericAtIndex_NOCAST(mut_parameter_index, nullptr); - else - selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT - } - if (origin_subpop_count != 1) { if (arg_origin_subpop->Type() == EidosValueType::kValueInt) @@ -2906,15 +2918,38 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID } MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); + Mutation *new_mut; - Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); // FIXME MULTITRAIT - - // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DFE), so we set all_pure_neutral_DFE_ also - if (selection_coeff != 0.0) + if (p_method_id == gID_addNewDrawnMutation) { - species->pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DFE_ = false; + new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, origin_subpop_id, origin_tick, (int8_t)nucleotide); + + // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ + if (!mutation_type_ptr->all_pure_neutral_DFE_) + species->pure_neutral_ = false; + } + else // (p_method_id == gID_addNewMutation) + { + slim_effect_t selection_coeff = singleton_selection_coeff; + + if (selcoeff_count != 1) + { + if (arg_selcoeff) + selection_coeff = (slim_effect_t)arg_selcoeff->NumericAtIndex_NOCAST(mut_parameter_index, nullptr); + else + selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT + } + + // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... + new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); + + // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ + // The selection coefficient was supplied by the user (i.e., not be from the mutation type's DFE), so we set all_pure_neutral_DFE_ also + if (selection_coeff != 0.0) + { + species->pure_neutral_ = false; + mutation_type_ptr->all_pure_neutral_DFE_ = false; + } } // add to the registry, return value, haplosome, etc. @@ -3389,7 +3424,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr for (int mut_index = 0; mut_index < segsites; ++mut_index) { slim_position_t position = positions[mut_index]; - double selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT + slim_effect_t selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT slim_objectid_t subpop_index = -1; slim_tick_t origin_tick = community.Tick(); int8_t nucleotide = -1; @@ -3407,7 +3442,8 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); - Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); // FIXME MULTITRAIT + // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... + Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ if (selection_coeff != 0.0) @@ -4061,6 +4097,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt else { // no mutation ID supplied, so use whatever is next + // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } @@ -4323,6 +4360,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID // Construct a vector of mutations to remove that is sorted by position std::vector mutations_to_remove; Mutation * const *mutations_data = (Mutation * const *)mutations_value->ObjectData(); + int trait_count = species->TraitCount(); for (int value_index = 0; value_index < mutations_count; ++value_index) { @@ -4336,8 +4374,20 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID mutations_to_remove.emplace_back(mut); - if (mut->selection_coeff_ != 0.0) - any_nonneutral_removed = true; + // If we're not already aware of having removed a non-neutral mutation, check on that now + if (!any_nonneutral_removed) + { + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (mut_trait_info[trait_index].effect_size_ != 0.0) + { + any_nonneutral_removed = true; + break; + } + } + } } std::sort(mutations_to_remove.begin(), mutations_to_remove.end(), [ ](Mutation *i1, Mutation *i2) {return i1->position_ < i2->position_;}); diff --git a/core/individual.cpp b/core/individual.cpp index 0dfbd23c..1698ba7c 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -3470,6 +3470,8 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb if (p_elements_size == 0) return gStaticEidosValue_Float_ZeroVec; + // FIXME MULTITRAIT: This should perhaps take a trait as its second parameter, so that it can be used with any trait; and its doc needs to be rewritten; and it should be deprecated + // SPECIES CONSISTENCY CHECK Species *species = Community::SpeciesForIndividualsVector((Individual **)p_elements, (int)p_elements_size); @@ -3482,7 +3484,8 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species->community_, species, "sumOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK // Sum the selection coefficients of mutations of the given type - Mutation *mut_block_ptr = species->SpeciesMutationBlock()->mutation_buffer_; + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_elements_size); int haplosome_count_per_individual = species->HaplosomeCountPerIndividual(); @@ -3507,12 +3510,16 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb int haplosome1_count = mutrun->size(); const MutationIndex *haplosome1_ptr = mutrun->begin_pointer_const(); - for (int mut_index = 0; mut_index < haplosome1_count; ++mut_index) + for (int index_in_mutrun = 0; index_in_mutrun < haplosome1_count; ++index_in_mutrun) { - Mutation *mut_ptr = mut_block_ptr + haplosome1_ptr[mut_index]; + MutationIndex mut_index = haplosome1_ptr[index_in_mutrun]; + Mutation *mut_ptr = mut_block_ptr + mut_index; if (mut_ptr->mutation_type_ptr_ == mutation_type_ptr) - selcoeff_sum += mut_ptr->selection_coeff_; + { + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mut_index); + selcoeff_sum += mut_trait_info[0].effect_size_; + } } } } @@ -5013,7 +5020,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (info_domcoeffs.size() > 0) dominance_coeff = info_domcoeffs[alt_allele_index]; else - dominance_coeff = static_cast(mutation_type_ptr->DefaultDominanceForTrait(0)); // FIXME MULTITRAIT + dominance_coeff = mutation_type_ptr->DefaultDominanceForTrait(0); // FIXME MULTITRAIT // get the selection coefficient from S, or draw one from the mutation type slim_effect_t selection_coeff; @@ -5021,7 +5028,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (info_selcoeffs.size() > 0) selection_coeff = info_selcoeffs[alt_allele_index]; else - selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT + selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT // get the subpop index from PO, or set to -1; no bounds checking on this slim_objectid_t subpop_index = -1; @@ -5108,6 +5115,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal else { // no mutation ID supplied, so use whatever is next + // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } diff --git a/core/mutation.cpp b/core/mutation.cpp index 3987c179..bbbc2b01 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -39,52 +39,82 @@ // A global counter used to assign all Mutation objects a unique ID slim_mutationid_t gSLiM_next_mutation_id = 0; +// This constructor is used when making a new mutation with effects and dominances provided by the caller; FIXME MULTITRAIT: needs to take a whole vector of each, per trait! Mutation::Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : -mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(p_selection_coeff), dominance_coeff_(p_dominance_coeff), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(gSLiM_next_mutation_id++) +mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(gSLiM_next_mutation_id++) { #ifdef DEBUG_LOCKS_ENABLED mutation_block_LOCK.start_critical(2); #endif Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); MutationBlock *mutation_block = species.SpeciesMutationBlock(); // initialize the tag to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; - // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); - // zero out our refcount and per-trait information, which is now kept in a separate buffer - // FIXME MULTITRAIT: The per-trait info will soon supplant selection_coeff_ and dominance_coeff_; this initialization code needs to be fleshed out MutationIndex mutation_index = mutation_block->IndexInBlock(this); mutation_block->refcount_buffer_[mutation_index] = 0; int trait_count = mutation_block->trait_count_; - MutationTraitInfo *traitInfoBase = mutation_block->trait_info_buffer_ + trait_count * mutation_index; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); + slim_effect_t hemizygous_dominance = mutation_type_ptr_->hemizygous_dominance_coeff_; + + // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since + // this is critical path. See those methods for more comments on what is happening here. + + is_neutral_ = true; // will be set to false below as needed for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - MutationTraitInfo *traitInfoRec = traitInfoBase + trait_index; - Trait *trait = species.Traits()[trait_index]; + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = traits[trait_index]; TraitType traitType = trait->Type(); - traitInfoRec->effect_size_ = selection_coeff_; - traitInfoRec->dominance_coeff_ = dominance_coeff_; + // FIXME MULTITRAIT: This constructor needs to change to have a whole vector of trait information passed in, for effect and dominance + slim_effect_t effect = trait_index ? p_selection_coeff : 0.0; + slim_effect_t dominance = trait_index ? p_dominance_coeff : 0.5; - if (traitType == TraitType::kMultiplicative) + traitInfoRec->effect_size_ = effect; + traitInfoRec->dominance_coeff_ = dominance; + + if (effect != 0.0) { - traitInfoRec->homozygous_effect_ = 0.0; - traitInfoRec->heterozygous_effect_ = 0.0; - traitInfoRec->hemizygous_effect_ = 0.0; + is_neutral_ = false; + + species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation + mutation_type_ptr_->all_pure_neutral_DFE_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + } } - else + else // (effect == 0.0) { - traitInfoRec->homozygous_effect_ = 0.0; - traitInfoRec->heterozygous_effect_ = 0.0; - traitInfoRec->hemizygous_effect_ = 0.0; + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } } } @@ -95,54 +125,130 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ #ifdef DEBUG_LOCKS_ENABLED mutation_block_LOCK.end_critical(); #endif +} + +// This constructor is used when making a new mutation with effects drawn from each trait's DES, and dominance taken from each trait's default dominance coefficient, both from the given mutation type +Mutation::Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : +mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(gSLiM_next_mutation_id++) +{ +#ifdef DEBUG_LOCKS_ENABLED + mutation_block_LOCK.start_critical(2); +#endif + + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + MutationBlock *mutation_block = species.SpeciesMutationBlock(); -#if 0 - // Dump the memory layout of a Mutation object. Note this code needs to be synced tightly with the header, since C++ has no real introspection. - static bool been_here = false; + // initialize the tag to the "unset" value + tag_value_ = SLIM_TAG_UNSET_VALUE; -#pragma omp critical (Mutation_layout_dump) + // zero out our refcount and per-trait information, which is now kept in a separate buffer + MutationIndex mutation_index = mutation_block->IndexInBlock(this); + mutation_block->refcount_buffer_[mutation_index] = 0; + + int trait_count = mutation_block->trait_count_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); + slim_effect_t hemizygous_dominance = mutation_type_ptr_->hemizygous_dominance_coeff_; + + // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since + // this is critical path. See those methods for more comments on what is happening here. + + is_neutral_ = true; // will be set to false below as needed + + if (mutation_type_ptr_->all_pure_neutral_DFE_) { - if (!been_here) + // The DFE of the mutation type is pure neutral, so we don't need to do any draws; we can short-circuit + // most of the work here and just set up neutral effects for all of the traits. + for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - char *ptr_base = (char *)this; - char *ptr_mutation_type_ptr_ = (char *)&(this->mutation_type_ptr_); - char *ptr_position_ = (char *)&(this->position_); - char *ptr_selection_coeff_ = (char *)&(this->selection_coeff_); - char *ptr_subpop_index_ = (char *)&(this->subpop_index_); - char *ptr_origin_tick_ = (char *)&(this->origin_tick_); - char *ptr_state_ = (char *)&(this->state_); - char *ptr_nucleotide_ = (char *)&(this->nucleotide_); - char *ptr_scratch_ = (char *)&(this->scratch_); - char *ptr_mutation_id_ = (char *)&(this->mutation_id_); - char *ptr_tag_value_ = (char *)&(this->tag_value_); - char *ptr_cached_one_plus_sel_ = (char *)&(this->cached_one_plus_sel_); - char *ptr_cached_one_plus_dom_sel_ = (char *)&(this->cached_one_plus_dom_sel_); - char *ptr_cached_one_plus_haploiddom_sel_ = (char *)&(this->cached_one_plus_haploiddom_sel_); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = traits[trait_index]; + TraitType traitType = trait->Type(); - std::cout << "Class Mutation memory layout (sizeof(Mutation) == " << sizeof(Mutation) << ") :" << std::endl << std::endl; - std::cout << " " << (ptr_mutation_type_ptr_ - ptr_base) << " (" << sizeof(MutationType *) << " bytes): MutationType *mutation_type_ptr_" << std::endl; - std::cout << " " << (ptr_position_ - ptr_base) << " (" << sizeof(slim_position_t) << " bytes): const slim_position_t position_" << std::endl; - std::cout << " " << (ptr_selection_coeff_ - ptr_base) << " (" << sizeof(slim_effect_t) << " bytes): slim_effect_t selection_coeff_" << std::endl; - std::cout << " " << (ptr_subpop_index_ - ptr_base) << " (" << sizeof(slim_objectid_t) << " bytes): slim_objectid_t subpop_index_" << std::endl; - std::cout << " " << (ptr_origin_tick_ - ptr_base) << " (" << sizeof(slim_tick_t) << " bytes): const slim_tick_t origin_tick_" << std::endl; - std::cout << " " << (ptr_state_ - ptr_base) << " (" << sizeof(int8_t) << " bytes): const int8_t state_" << std::endl; - std::cout << " " << (ptr_nucleotide_ - ptr_base) << " (" << sizeof(int8_t) << " bytes): const int8_t nucleotide_" << std::endl; - std::cout << " " << (ptr_scratch_ - ptr_base) << " (" << sizeof(int8_t) << " bytes): const int8_t scratch_" << std::endl; - std::cout << " " << (ptr_mutation_id_ - ptr_base) << " (" << sizeof(slim_mutationid_t) << " bytes): const slim_mutationid_t mutation_id_" << std::endl; - std::cout << " " << (ptr_tag_value_ - ptr_base) << " (" << sizeof(slim_usertag_t) << " bytes): slim_usertag_t tag_value_" << std::endl; - std::cout << " " << (ptr_cached_one_plus_sel_ - ptr_base) << " (" << sizeof(slim_effect_t) << " bytes): slim_effect_t cached_one_plus_sel_" << std::endl; - std::cout << " " << (ptr_cached_one_plus_dom_sel_ - ptr_base) << " (" << sizeof(slim_effect_t) << " bytes): slim_effect_t cached_one_plus_dom_sel_" << std::endl; - std::cout << " " << (ptr_cached_one_plus_haploiddom_sel_ - ptr_base) << " (" << sizeof(slim_effect_t) << " bytes): slim_effect_t cached_one_plus_haploiddom_sel_" << std::endl; - std::cout << std::endl; + traitInfoRec->effect_size_ = 0.0; + traitInfoRec->dominance_coeff_ = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); - been_here = true; + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } } } + else + { + // The DFE of the mutation type is not pure neutral. Note that species.pure_neutral_ might still be true + // at this point; the mutation type for this mutation might not be used by any genomic element type, + // because we might be getting called by addNewDrawn() mutation for a type that is otherwise unused. + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = traits[trait_index]; + TraitType traitType = trait->Type(); + + slim_effect_t effect = mutation_type_ptr_->DrawEffectForTrait(trait_index); + slim_effect_t dominance = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); + + traitInfoRec->effect_size_ = effect; + traitInfoRec->dominance_coeff_ = dominance; + + if (effect != 0.0) + { + is_neutral_ = false; + + species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + } + } + else // (effect == 0.0) + { + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } + } + } + } + +#if DEBUG_MUTATIONS + std::cout << "Mutation constructed: " << this << std::endl; +#endif + +#ifdef DEBUG_LOCKS_ENABLED + mutation_block_LOCK.end_critical(); #endif } +// This constructor is used when making a new mutation with effects and dominances provided by the caller, *and* a mutation id provided by the caller Mutation::Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide) : -mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(p_selection_coeff), dominance_coeff_(p_dominance_coeff), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id) +mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id) { Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); @@ -150,39 +256,67 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ // initialize the tag to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; - // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); - // zero out our refcount and per-trait information, which is now kept in a separate buffer - // FIXME MULTITRAIT: The per-trait info will soon supplant selection_coeff_ and dominance_coeff_; this initialization code needs to be fleshed out MutationIndex mutation_index = mutation_block->IndexInBlock(this); mutation_block->refcount_buffer_[mutation_index] = 0; int trait_count = mutation_block->trait_count_; - MutationTraitInfo *traitInfoBase = mutation_block->trait_info_buffer_ + trait_count * mutation_index; + MutationTraitInfo *mut_trait_info = mutation_block->trait_info_buffer_ + trait_count * mutation_index; + slim_effect_t hemizygous_dominance = mutation_type_ptr_->hemizygous_dominance_coeff_; + + // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since + // this is critical path. See those methods for more comments on what is happening here. + + is_neutral_ = true; // will be set to false by EffectChanged() as needed for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - MutationTraitInfo *traitInfoRec = traitInfoBase + trait_index; + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; Trait *trait = species.Traits()[trait_index]; TraitType traitType = trait->Type(); - traitInfoRec->effect_size_ = selection_coeff_; - traitInfoRec->dominance_coeff_ = dominance_coeff_; + // FIXME MULTITRAIT: The per-trait info will soon supplant selection_coeff_ and dominance_coeff_; this initialization code needs to be fleshed out + slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : 0.0; + slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : 0.5; - if (traitType == TraitType::kMultiplicative) + traitInfoRec->effect_size_ = effect; + traitInfoRec->dominance_coeff_ = dominance; + + if (effect != 0.0) { - traitInfoRec->homozygous_effect_ = 0.0; - traitInfoRec->heterozygous_effect_ = 0.0; - traitInfoRec->hemizygous_effect_ = 0.0; + is_neutral_ = false; + + species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation + mutation_type_ptr_->all_pure_neutral_DFE_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + } } - else + else // (effect == 0.0) { - traitInfoRec->homozygous_effect_ = 0.0; - traitInfoRec->heterozygous_effect_ = 0.0; - traitInfoRec->hemizygous_effect_ = 0.0; + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } } } @@ -191,7 +325,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ #endif // Since a mutation id was supplied by the caller, we need to ensure that subsequent mutation ids generated do not collide - // This constructor (unline the other Mutation() constructor above) is presently never called multithreaded, + // This constructor (unlike the other Mutation() constructor above) is presently never called multithreaded, // so we just enforce that here. If that changes, it should start using the debug lock to detect races, as above. THREAD_SAFETY_IN_ACTIVE_PARALLEL("Mutation::Mutation(): gSLiM_next_mutation_id change"); @@ -199,6 +333,117 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ gSLiM_next_mutation_id = mutation_id_ + 1; } +// This should be called whenever a mutation effect is changed; it handles the necessary recaching +void Mutation::SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect) +{ + slim_effect_t old_effect = traitInfoRec->effect_size_; + slim_effect_t dominance = traitInfoRec->dominance_coeff_; + + traitInfoRec->effect_size_ = p_new_effect; + + if (p_new_effect != 0.0) + { + if (old_effect == 0.0) + { + // This mutation is no longer neutral; various observers care about that change + is_neutral_ = false; + + Species &species = mutation_type_ptr_->species_; + + species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation + mutation_type_ptr_->all_pure_neutral_DFE_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + } + + // cache values used by the fitness calculation code for speed; see header + // FIXME MULTICHROM: the hemizygous dominance coeff for a given mutation type could/should be per-trait; + // we cache hemizygous_effect_ for each trait separately anyway, so there's no waste there... + slim_effect_t hemizygous_dominance = mutation_type_ptr_->hemizygous_dominance_coeff_; + + if (traitType == TraitType::kMultiplicative) + { + // For multiplicative traits, we clamp the lower end to 0.0; you can't be more lethal than lethal, and we + // never want to go negative and then go positive again by multiplying in another negative effect. There + // is admittedly a philosophical issue here; if a multiplicative trait represented simply some abstract + // trait with no direct connection to fitness, then maybe clamping here would not make sense? But even + // then, negative effects don't really seem to me to make sense there, so I think this is good. + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_effect); // 1 + s + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * p_new_effect); // 1 + hs + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * p_new_effect); // 1 + hs (using hemizygous h) + } + else // (traitType == TraitType::kAdditive) + { + // For additive traits, the baseline of the trait is arbitrary and there is no cutoff. + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * p_new_effect); // 2a + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * p_new_effect); // 2ha + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * p_new_effect); // 2ha (using hemizygous h) + } + } + else // p_new_effect == 0.0 + { + if (old_effect != 0.0) + { + // Changing from non-neutral to neutral; various observers care about that + // Note that we cannot set is_neutral_ and other such flags to true here, because only this trait's + // effect has changed to neutral; other trait effects might be non-neutral, which we don't check + Species &species = mutation_type_ptr_->species_; + + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + + // cache values used by the fitness calculation code for speed; see header + // for a neutral trait, we can set up this info very quickly + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } + } + } +} + +// This should be called whenever a mutation dominance is changed; it handles the necessary recaching +void Mutation::SetDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) +{ + traitInfoRec->dominance_coeff_ = p_new_dominance; + + // We only need to recache the heterozygous_effect_ values, since only they are affected by the change in + // dominance coefficient. Changing dominance has no effect on is_neutral_ or any of the other is-neutral + // flags. So this is very simple. + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_dominance * traitInfoRec->effect_size_); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); + } +} + +void Mutation::HemizygousDominanceChanged(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) +{ + // The hemizygous dominance coefficient is kept by the mutation type, so this does not actually set it, + // unlike SetEffect() and SetDominance() above. This is called from MutationType::SetProperty() when + // the hemizygous dominance coefficient changes, to ask us to recache fitness values. As with the + // SetDominance() method above, this has no effect on is_neutral_ and similar. + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_dominance * traitInfoRec->effect_size_); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); + } +} + void Mutation::SelfDelete(void) { // This is called when our retain count reaches zero @@ -214,7 +459,11 @@ void Mutation::SelfDelete(void) // This is unused except by debugging code and in the debugger itself std::ostream &operator<<(std::ostream &p_outstream, const Mutation &p_mutation) { - p_outstream << "Mutation{mutation_type_ " << p_mutation.mutation_type_ptr_->mutation_type_id_ << ", position_ " << p_mutation.position_ << ", selection_coeff_ " << p_mutation.selection_coeff_ << ", subpop_index_ " << p_mutation.subpop_index_ << ", origin_tick_ " << p_mutation.origin_tick_; + Species &species = p_mutation.mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(&p_mutation); + + p_outstream << "Mutation{mutation_type_ " << p_mutation.mutation_type_ptr_->mutation_type_id_ << ", position_ " << p_mutation.position_ << ", effect_size_ " << mut_trait_info[0].effect_size_ << ", subpop_index_ " << p_mutation.subpop_index_ << ", origin_tick_ " << p_mutation.origin_tick_; return p_outstream; } @@ -234,7 +483,8 @@ const EidosClass *Mutation::Class(void) const void Mutation::Print(std::ostream &p_ostream) const { - p_ostream << Class()->ClassNameForDisplay() << "<" << mutation_id_ << ":" << selection_coeff_ << ">"; + // BCH 10/20/2025: Changing from selection_coeff_ to position_ here, as part of multitrait work + p_ostream << Class()->ClassNameForDisplay() << "<" << mutation_id_ << ":" << position_ << ">"; } EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) @@ -269,13 +519,12 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationIndex mut_index = mutation_block->IndexInBlock(this); - MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); const std::vector &traits = species.Traits(); size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selection_coeff_)); // FIXME MULTITRAIT + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[0].effect_size_)); else if (trait_count == 0) return gStaticEidosValue_Float_ZeroVec; else @@ -284,7 +533,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) { - slim_effect_t effect = trait_info[trait_index].effect_size_; + slim_effect_t effect = mut_trait_info[trait_index].effect_size_; float_result->push_float_no_check(effect); } @@ -298,13 +547,12 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationIndex mut_index = mutation_block->IndexInBlock(this); - MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); const std::vector &traits = species.Traits(); size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance_coeff_)); // FIXME MULTITRAIT + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[0].dominance_coeff_)); else if (trait_count == 0) return gStaticEidosValue_Float_ZeroVec; else @@ -313,7 +561,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) { - slim_effect_t dominance = trait_info[trait_index].dominance_coeff_; + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_; float_result->push_float_no_check(dominance); } @@ -370,8 +618,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationIndex mut_index = mutation_block->IndexInBlock(this); - MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) @@ -380,7 +627,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) Trait *trait = species.TraitFromName(trait_name); if (trait) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info[trait->Index()].effect_size_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].effect_size_)); } else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) { @@ -388,7 +635,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) Trait *trait = species.TraitFromName(trait_name); if (trait) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info[trait->Index()].dominance_coeff_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].dominance_coeff_)); } return super::GetProperty(p_property_id); @@ -615,8 +862,7 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationIndex mut_index = mutation_block->IndexInBlock(this); - MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) @@ -626,7 +872,10 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & if (trait) { - trait_info[trait->Index()].effect_size_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); + slim_effect_t new_effect = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + + SetEffect(trait->Type(), traitInfoRec, new_effect); return; } } @@ -637,7 +886,10 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & if (trait) { - trait_info[trait->Index()].dominance_coeff_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); + slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + + SetDominance(trait->Type(), traitInfoRec, new_dominance); return; } } @@ -711,13 +963,12 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho // get the trait info for this mutation MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationIndex mut_index = mutation_block->IndexInBlock(this); - MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); if (trait_indices.size() == 1) { int64_t trait_index = trait_indices[0]; - slim_effect_t effect = trait_info[trait_index].effect_size_; + slim_effect_t effect = mut_trait_info[trait_index].effect_size_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(effect)); } @@ -727,7 +978,7 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho for (int64_t trait_index : trait_indices) { - slim_effect_t effect = trait_info[trait_index].effect_size_; + slim_effect_t effect = mut_trait_info[trait_index].effect_size_; float_result->push_float_no_check(effect); } @@ -750,13 +1001,12 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me // get the trait info for this mutation MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationIndex mut_index = mutation_block->IndexInBlock(this); - MutationTraitInfo *trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); if (trait_indices.size() == 1) { int64_t trait_index = trait_indices[0]; - slim_effect_t dominance = trait_info[trait_index].dominance_coeff_; + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); } @@ -766,7 +1016,7 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me for (int64_t trait_index : trait_indices) { - slim_effect_t dominance = trait_info[trait_index].dominance_coeff_; + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_; float_result->push_float_no_check(dominance); } @@ -792,13 +1042,26 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth mutation_type_ptr_ = mutation_type_ptr; // If we are non-neutral, make sure the mutation type knows it is now also non-neutral - if (selection_coeff_ != 0.0) + // FIXME MULTITRAIT: I think it might be useful for MutationType to keep a flag separately for each trait, whether *that* trait is all_pure_neutral_DFE_ or not + int trait_count = species.TraitCount(); + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + + if (!is_neutral_) mutation_type_ptr_->all_pure_neutral_DFE_ = false; - // cache values used by the fitness calculation code for speed; see header - cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + selection_coeff_); - cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dominance_coeff_ * selection_coeff_); - cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + // Cache values used by the fitness calculation code for speed; changing the mutation type no longer changes + // the dominance coefficient, but hemizygous_dominance_coeff_ still comes from the muttype, and so might + // have changed. Note that is_neutral_ and similar do not change as a result of this, since the mutation + // effect remains the same (neutral or non-neutral). + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = species.Traits()[trait_index]; + TraitType traitType = trait->Type(); + + HemizygousDominanceChanged(traitType, traitInfoRec, mutation_type_ptr_->hemizygous_dominance_coeff_); + } return gStaticEidosValueVOID; } @@ -900,6 +1163,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): setEffectForTrait() requires that all mutations belong to the same species." << EidosTerminate(); MutationBlock *mutation_block = species->SpeciesMutationBlock(); + const std::vector &traits = species->Traits(); // get the trait indices, with bounds-checking std::vector trait_indices; @@ -915,11 +1179,11 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI { Mutation *mut = mutations_buffer[mutation_index]; MutationType *muttype = mut->mutation_type_ptr_; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t effect = (slim_effect_t)muttype->DrawEffectForTrait(trait_index); - mut_trait_info[trait_index].effect_size_ = effect; + mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); } } } @@ -936,10 +1200,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut_trait_info[trait_index].effect_size_ = effect; + mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); } } else @@ -947,11 +1211,14 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); for (int64_t trait_index : trait_indices) - mut_trait_info[trait_index].effect_size_ = effect; + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + + mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + } } } } @@ -967,10 +1234,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut_trait_info[trait_index].effect_size_ = effect; + mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); } } } @@ -991,10 +1258,11 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t effect = static_cast(*(effects_int++)); - mut_trait_info[trait_index].effect_size_ = static_cast(*(effects_int++)); + mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); } } else @@ -1002,11 +1270,15 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); for (int64_t trait_index : trait_indices) - mut_trait_info[trait_index].effect_size_ = static_cast(*(effects_int++)); + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t effect = static_cast(*(effects_int++)); + + mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + } } } } @@ -1023,10 +1295,11 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t effect = static_cast(*(effects_float++)); - mut_trait_info[trait_index].effect_size_ = static_cast(*(effects_float++)); + mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); } } else @@ -1034,11 +1307,15 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); for (int64_t trait_index : trait_indices) - mut_trait_info[trait_index].effect_size_ = static_cast(*(effects_float++)); + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t effect = static_cast(*(effects_float++)); + + mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + } } } } @@ -1072,6 +1349,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setDominanceForTrait): setDominanceForTrait() requires that all mutations belong to the same species." << EidosTerminate(); MutationBlock *mutation_block = species->SpeciesMutationBlock(); + const std::vector &traits = species->Traits(); // get the trait indices, with bounds-checking std::vector trait_indices; @@ -1088,11 +1366,11 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri { Mutation *mut = mutations_buffer[mutation_index]; MutationType *muttype = mut->mutation_type_ptr_; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); - slim_effect_t dominance = (slim_effect_t)muttype->DefaultDominanceForTrait(trait_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t dominance = muttype->DefaultDominanceForTrait(trait_index); - mut_trait_info[trait_index].dominance_coeff_ = dominance; + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } } @@ -1109,10 +1387,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut_trait_info[trait_index].dominance_coeff_ = dominance; + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } else @@ -1120,11 +1398,14 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); for (int64_t trait_index : trait_indices) - mut_trait_info[trait_index].dominance_coeff_ = dominance; + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + } } } } @@ -1140,10 +1421,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut_trait_info[trait_index].dominance_coeff_ = dominance; + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } } @@ -1164,10 +1445,11 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t dominance = static_cast(*(dominances_int++)); - mut_trait_info[trait_index].dominance_coeff_ = static_cast(*(dominances_int++)); + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } else @@ -1175,11 +1457,15 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); for (int64_t trait_index : trait_indices) - mut_trait_info[trait_index].dominance_coeff_ = static_cast(*(dominances_int++)); + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t dominance = static_cast(*(dominances_int++)); + + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + } } } } @@ -1196,10 +1482,11 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t dominance = static_cast(*(dominances_float++)); - mut_trait_info[trait_index].dominance_coeff_ = static_cast(*(dominances_float++)); + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } else @@ -1207,11 +1494,15 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { Mutation *mut = mutations_buffer[mutation_index]; - MutationIndex mut_index = mutation_block->IndexInBlock(mut); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); for (int64_t trait_index : trait_indices) - mut_trait_info[trait_index].dominance_coeff_ = static_cast(*(dominances_float++)); + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t dominance = static_cast(*(dominances_float++)); + + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + } } } } diff --git a/core/mutation.h b/core/mutation.h index 58f58b80..694a848f 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -66,9 +66,9 @@ typedef struct _MutationTraitInfo // when it is homozygous or heterozygous, respectively. These values are clamped to a minimum of 0.0, so that multiplying // by them cannot cause the fitness of the individual to go below 0.0, avoiding slow tests in the core fitness loop. These // values use slim_effect_t for speed; roundoff should not be a concern, since such differences would be inconsequential. - slim_effect_t homozygous_effect_; // a cached value for (1 + selection_coeff_), clamped to 0.0 minimum - slim_effect_t heterozygous_effect_; // a cached value for (1 + dominance_coeff * selection_coeff_), clamped to 0.0 minimum - slim_effect_t hemizygous_effect_; // a cached value for (1 + hemizygous_dominance_coeff_ * selection_coeff_), clamped to 0.0 minimum + slim_effect_t homozygous_effect_; // a cached value for 1 + s, clamped to 0.0 minimum; OR for 2a + slim_effect_t heterozygous_effect_; // a cached value for 1 + hs, clamped to 0.0 minimum; OR for 2ha + slim_effect_t hemizygous_effect_; // a cached value for 1 + hs, clamped to 0.0 minimum; OR for 2ha (h = hemizygous_dominance_coeff_) } MutationTraitInfo; typedef enum { @@ -90,12 +90,17 @@ class Mutation : public EidosDictionaryRetained MutationType *mutation_type_ptr_; // mutation type identifier const slim_position_t position_; // position on the chromosome - slim_effect_t selection_coeff_; // selection coefficient (s) - slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default slim_objectid_t subpop_index_; // subpopulation in which mutation arose (or a user-defined tag value!) const slim_tick_t origin_tick_; // tick in which the mutation arose slim_chromosome_index_t chromosome_index_; // the (uint8_t) index of this mutation's chromosome - int8_t state_; // see MutationState above + int state_ : 4; // see MutationState above; 4 bits so we can represent -1 + + // is_neutral_ is true if all mutation effects are 0.0 (note this might be overridden by a callback); + // the 0 state is sticky, so if the mutation is ever marked non-neutral then it stays marked non-neutral, + // just because re-evaluating that requires scanning across the effects for all traits -- not worth it + // this is used to make constructing non-neutral caches for fitness evaluation fast with multiple traits + unsigned int is_neutral_ : 1; + int8_t nucleotide_; // the nucleotide being kept: A=0, C=1, G=2, T=3. -1 is used to indicate non-nucleotide-based. int8_t scratch_; // temporary scratch space for use by algorithms; regard as volatile outside your own code block const slim_mutationid_t mutation_id_; // a unique id for each mutation, used to track mutations @@ -106,14 +111,6 @@ class Mutation : public EidosDictionaryRetained mutable slim_refcount_t gui_scratch_reference_count_; // an additional refcount used for temporary tallies by SLiMgui, valid only when explicitly updated #endif - // We cache values used in the fitness calculation code, for speed. These are the final fitness effects of this mutation - // when it is homozygous or heterozygous, respectively. These values are clamped to a minimum of 0.0, so that multiplying - // by them cannot cause the fitness of the individual to go below 0.0, avoiding slow tests in the core fitness loop. These - // values use slim_effect_t for speed; roundoff should not be a concern, since such differences would be inconsequential. - slim_effect_t cached_one_plus_sel_; // a cached value for (1 + selection_coeff_), clamped to 0.0 minimum - slim_effect_t cached_one_plus_dom_sel_; // a cached value for (1 + dominance_coeff * selection_coeff_), clamped to 0.0 minimum - slim_effect_t cached_one_plus_hemizygousdom_sel_; // a cached value for (1 + hemizygous_dominance_coeff_ * selection_coeff_), clamped to 0.0 minimum - #if DEBUG mutable slim_refcount_t refcount_CHECK_; // scratch space for checking of parallel refcounting #endif @@ -121,9 +118,23 @@ class Mutation : public EidosDictionaryRetained Mutation(const Mutation&) = delete; // no copying Mutation& operator=(const Mutation&) = delete; // no copying Mutation(void) = delete; // no null construction; Mutation is an immutable class + + // This constructor is used when making a new mutation with effects DRAWN from each trait's DES, and dominance taken from each trait's default dominance coefficient, both from the given mutation type + Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); + + // This constructor is used when making a new mutation with effects and dominances PROVIDED by the caller + // FIXME MULTITRAIT: needs to take a whole vector of each, per trait! Mutation(MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); + + // This constructor is used when making a new mutation with effects and dominances PROVIDED by the caller, AND a mutation id provided by the caller + // FIXME MULTITRAIT: needs to take a whole vector of each, per trait! Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); + // These should be called whenever a mutation effect/dominance is changed; they handle the necessary recaching + void SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect); + void SetDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); + void HemizygousDominanceChanged(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); + // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline #if DEBUG_MUTATIONS inline virtual ~Mutation(void) override diff --git a/core/mutation_block.h b/core/mutation_block.h index 596c6853..ba8db9a3 100644 --- a/core/mutation_block.h +++ b/core/mutation_block.h @@ -69,8 +69,13 @@ class MutationBlock inline __attribute__((always_inline)) Mutation *MutationForIndex(MutationIndex p_index) const { return mutation_buffer_ + p_index; } inline __attribute__((always_inline)) slim_refcount_t RefcountForIndex(MutationIndex p_index) const { return refcount_buffer_[p_index]; } - inline __attribute__((always_inline)) MutationTraitInfo *TraitInfoIndex(MutationIndex p_index) const { return trait_info_buffer_ + (p_index * trait_count_); } + inline __attribute__((always_inline)) MutationTraitInfo *TraitInfoForIndex(MutationIndex p_index) const { return trait_info_buffer_ + (p_index * trait_count_); } + inline __attribute__((always_inline)) MutationTraitInfo *TraitInfoForMutation(const Mutation *p_mutation) const + { + MutationIndex mut_index = (MutationIndex)(p_mutation - mutation_buffer_); + return trait_info_buffer_ + (mut_index * trait_count_); + } inline __attribute__((always_inline)) MutationIndex IndexInBlock(const Mutation *p_mutation) const { return (MutationIndex)(p_mutation - mutation_buffer_); diff --git a/core/mutation_run.cpp b/core/mutation_run.cpp index dd6e0c16..1fc437fc 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -456,14 +456,25 @@ void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; + Mutation *mutptr = p_mut_block_ptr + mutindex; - if ((p_mut_block_ptr + mutindex)->selection_coeff_ != 0.0) + if (!mutptr->is_neutral_) add_to_nonneutral_buffer(mutindex); } } void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const { + // FIXME MULTICHROM: I think regime 2 needs to be rethought with multitrait. We won't have + // constant mutationEffect() callbacks any more; all of those optimizations, including regime 2, + // can be ripped out. Instead, QTL mutations will contribute an additive effect to a quantitative + // trait, and their effect on whatever multiplicative trait might be in the model will be zero + // (absent pleiotropy). That is the case that we will now want to detect and optimize somehow. + // I'm not sure what the right strategy would be. What exactly will the role of non-neutral + // caches be? Should mutations that are non-neutral for *any* trait be put into them, which would + // be best for universal pleiotropy? Or maybe we have separate non-neutral caches for each trait, + // which would be best for zero pleiotropy? Or some kind of adaptive approach? + // // Regime 2 means the only mutationEffect() callbacks are (a) constant-effect, (b) neutral (i.e., // make their mutation type become neutral), and (c) global (i.e. apply to all subpopulations). @@ -483,7 +494,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) // The result of && is not order-dependent, but the first condition is checked first. // I expect many mutations would fail the first test (thus short-circuiting), whereas // few would fail the second test (i.e. actually be 0.0) in a QTL model. - if ((!mutptr->mutation_type_ptr_->set_neutral_by_global_active_callback_) && (mutptr->selection_coeff_ != 0.0)) + if ((!mutptr->mutation_type_ptr_->set_neutral_by_global_active_callback_) && !mutptr->is_neutral_) add_to_nonneutral_buffer(mutindex); } } @@ -508,7 +519,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) // The result of || is not order-dependent, but the first condition is checked first. // I have reordered this to put the fast test first; or I'm guessing it's the fast test. - if ((mutptr->selection_coeff_ != 0.0) || (mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_)) + if (!mutptr->is_neutral_ || (mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_)) add_to_nonneutral_buffer(mutindex); } } diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 44e38b7d..69ede9cd 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -26,6 +26,7 @@ #include "slim_eidos_block.h" #include "species.h" #include "community.h" +#include "mutation_block.h" #include #include @@ -247,14 +248,7 @@ void MutationType::ParseDFEParameters(std::string &p_dfe_type_string, const Eido } } -double MutationType::DefaultDominanceForTrait(int64_t p_trait_index) const -{ - const EffectDistributionInfo &de_info = effect_distributions_[p_trait_index]; - - return de_info.default_dominance_coeff_; -} - -double MutationType::DrawEffectForTrait(int64_t p_trait_index) const +slim_effect_t MutationType::DrawEffectForTrait(int64_t p_trait_index) const { const EffectDistributionInfo &de_info = effect_distributions_[p_trait_index]; @@ -264,36 +258,36 @@ double MutationType::DrawEffectForTrait(int64_t p_trait_index) const // So here and in similar places, we fetch the RNG rather than passing it in to keep single-threaded fast. switch (de_info.dfe_type_) { - case DFEType::kFixed: return de_info.dfe_parameters_[0]; + case DFEType::kFixed: return static_cast(de_info.dfe_parameters_[0]); case DFEType::kGamma: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_gamma(rng, de_info.dfe_parameters_[1], de_info.dfe_parameters_[0] / de_info.dfe_parameters_[1]); + return static_cast(gsl_ran_gamma(rng, de_info.dfe_parameters_[1], de_info.dfe_parameters_[0] / de_info.dfe_parameters_[1])); } case DFEType::kExponential: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_exponential(rng, de_info.dfe_parameters_[0]); + return static_cast(gsl_ran_exponential(rng, de_info.dfe_parameters_[0])); } case DFEType::kNormal: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_gaussian(rng, de_info.dfe_parameters_[1]) + de_info.dfe_parameters_[0]; + return static_cast(gsl_ran_gaussian(rng, de_info.dfe_parameters_[1]) + de_info.dfe_parameters_[0]); } case DFEType::kWeibull: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_weibull(rng, de_info.dfe_parameters_[0], de_info.dfe_parameters_[1]); + return static_cast(gsl_ran_weibull(rng, de_info.dfe_parameters_[0], de_info.dfe_parameters_[1])); } case DFEType::kLaplace: { gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_laplace(rng, de_info.dfe_parameters_[1]) + de_info.dfe_parameters_[0]; + return static_cast(gsl_ran_laplace(rng, de_info.dfe_parameters_[1]) + de_info.dfe_parameters_[0]); } case DFEType::kScript: @@ -407,7 +401,7 @@ double MutationType::DrawEffectForTrait(int64_t p_trait_index) const DrawEffectForTrait_InterpreterLock.end_critical(); #endif - return sel_coeff; + return static_cast(sel_coeff); } } EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectForTrait): (internal error) unexpected dfe_type_ value." << EidosTerminate(); @@ -604,9 +598,40 @@ void MutationType::SetProperty(EidosGlobalStringID p_property_id, const EidosVal hemizygous_dominance_coeff_ = static_cast(value); // intentionally no bounds check - // Changing the hemizygous dominance coefficient means that the cached fitness effects of all mutations using this type - // become invalid. We set a flag here to indicate that values that depend on us need to be recached. - species_.any_dominance_coeff_changed_ = true; + // Changing the hemizygous dominance coefficient means that the cached hemizygous fitness effects of + // all mutations using this type become invalid. We recache correct values for those mutations here. + // This is heavyweight for a property, but it is much simpler than having a deferred recache scheme, + // and changing the hemizygous dominance coefficient is expected to be extremely infrequent. + { + Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; + int registry_size; + const MutationIndex *registry_iter = species_.population_.MutationRegistry(®istry_size); + const MutationIndex *registry_iter_end = registry_iter + registry_size; + + while (registry_iter != registry_iter_end) + { + MutationIndex mut_index = (*registry_iter++); + Mutation *mut = mut_block_ptr + mut_index; + + if (mut->mutation_type_ptr_ == this) + { + MutationTraitInfo *mut_trait_info = mutation_block_->TraitInfoForIndex(mut_index); + + // loop over the traits and validate the cached hemizygous effect for each one + const std::vector &traits = species_.Traits(); + size_t trait_count = traits.size(); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + Trait *trait = traits[trait_index]; + + mut->HemizygousDominanceChanged(trait->Type(), mut_trait_info + trait_index, hemizygous_dominance_coeff_); + } + } + } + } + + // We also let the community know that a mutation type changed, for GUI redisplay species_.community_.mutation_types_changed_ = true; return; @@ -914,7 +939,6 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba // effects of all mutations using this type become invalid; it is now just the *default* coefficient, // and changing it does not change the state of mutations that have already derived from it. We do // still want to let the community know that a mutation type has changed, though. - //species_.any_dominance_coeff_changed_ = true; species_.community_.mutation_types_changed_ = true; return gStaticEidosValueVOID; diff --git a/core/mutation_type.h b/core/mutation_type.h index 5e75cdd8..7b7f4cb0 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -182,8 +182,14 @@ class MutationType : public EidosDictionaryUnretained static void ParseDFEParameters(std::string &p_dfe_type_string, const EidosValue_SP *const p_arguments, int p_argument_count, DFEType *p_dfe_type, std::vector *p_dfe_parameters, std::vector *p_dfe_strings); - double DefaultDominanceForTrait(int64_t p_trait_index) const; // get the default dominance coefficient for a trait - double DrawEffectForTrait(int64_t p_trait_index) const; // draw a selection coefficient from the DE for a trait + slim_effect_t DefaultDominanceForTrait(int64_t p_trait_index) const + { + const EffectDistributionInfo &de_info = effect_distributions_[p_trait_index]; + + return de_info.default_dominance_coeff_; + } + + slim_effect_t DrawEffectForTrait(int64_t p_trait_index) const; // draw a selection coefficient from the DE for a trait bool IsPureNeutralDFE(void) const { return all_pure_neutral_DFE_; } diff --git a/core/polymorphism.cpp b/core/polymorphism.cpp index 8a5d5855..d5d2c21b 100644 --- a/core/polymorphism.cpp +++ b/core/polymorphism.cpp @@ -20,6 +20,7 @@ #include "polymorphism.h" #include "species.h" +#include "mutation_block.h" #include #include @@ -45,13 +46,32 @@ void Polymorphism::Print_ID_Tag(std::ostream &p_out) const p_out << polymorphism_id_ << " " << mutation_ptr_->mutation_id_ << " " << "m" << mutation_ptr_->mutation_type_ptr_->mutation_type_id_ << " " << mutation_ptr_->position_ << " "; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mutation_ptr_->selection_coeff_); // necessary precision for non-lossiness - p_out << double_buf; + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + Species &species = mutation_ptr_->mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].effect_size_); // necessary precision for non-lossiness + p_out << double_buf; + } p_out << " "; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mutation_ptr_->dominance_coeff_); // necessary precision for non-lossiness - p_out << double_buf; + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].dominance_coeff_); // necessary precision for non-lossiness + p_out << double_buf; + } p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -80,13 +100,32 @@ void Polymorphism::Print_ID(std::ostream &p_out) const p_out << polymorphism_id_ << " " << mutation_ptr_->mutation_id_ << " " << "m" << mutation_ptr_->mutation_type_ptr_->mutation_type_id_ << " " << mutation_ptr_->position_ << " "; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mutation_ptr_->selection_coeff_); // necessary precision for non-lossiness - p_out << double_buf; + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + Species &species = mutation_ptr_->mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].effect_size_); // necessary precision for non-lossiness + p_out << double_buf; + } p_out << " "; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mutation_ptr_->dominance_coeff_); // necessary precision for non-lossiness - p_out << double_buf; + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].dominance_coeff_); // necessary precision for non-lossiness + p_out << double_buf; + } p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -115,8 +154,34 @@ void Polymorphism::Print_NoID_Tag(std::ostream &p_out) const p_out << " \"" << chromosome->Symbol() << "\""; } + p_out << " "; + + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + p_out << mut_trait_info[trait_index].effect_size_; + } + + p_out << " "; + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + p_out << mut_trait_info[trait_index].dominance_coeff_; + } + // and then the remainder of the output line - p_out << " " << mutation_ptr_->selection_coeff_ << " " << mutation_ptr_->dominance_coeff_ << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; + p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; // output a nucleotide if available if (mutation_ptr_->mutation_type_ptr_->nucleotide_based_) @@ -151,8 +216,34 @@ void Polymorphism::Print_NoID(std::ostream &p_out) const p_out << " \"" << chromosome->Symbol() << "\""; } + p_out << " "; + + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + p_out << mut_trait_info[trait_index].effect_size_; + } + + p_out << " "; + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + if (trait_index > 0) + p_out << ","; + + p_out << mut_trait_info[trait_index].dominance_coeff_; + } + // and then the remainder of the output line - p_out << " " << mutation_ptr_->selection_coeff_ << " " << mutation_ptr_->dominance_coeff_ << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; + p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; // output a nucleotide if available if (mutation_ptr_->mutation_type_ptr_->nucleotide_based_) diff --git a/core/population.cpp b/core/population.cpp index ffa8885d..e22a1a84 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5190,27 +5190,6 @@ void Population::AddTallyForMutationTypeAndBinNumber(int p_mutation_type_index, } #endif -void Population::ValidateMutationFitnessCaches(void) -{ - Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; - int registry_size; - const MutationIndex *registry_iter = MutationRegistry(®istry_size); - const MutationIndex *registry_iter_end = registry_iter + registry_size; - - while (registry_iter != registry_iter_end) - { - MutationIndex mut_index = (*registry_iter++); - Mutation *mut = mut_block_ptr + mut_index; - slim_effect_t sel_coeff = mut->selection_coeff_; - slim_effect_t dom_coeff = mut->dominance_coeff_; - slim_effect_t hemizygous_dom_coeff = mut->mutation_type_ptr_->hemizygous_dominance_coeff_; - - mut->cached_one_plus_sel_ = (slim_effect_t)std::max(0.0, 1.0 + sel_coeff); - mut->cached_one_plus_dom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + dom_coeff * sel_coeff); - mut->cached_one_plus_hemizygousdom_sel_ = (slim_effect_t)std::max(0.0, 1.0 + hemizygous_dom_coeff * sel_coeff); - } -} - void Population::RecalculateFitness(slim_tick_t p_tick) { // calculate the fitnesses of the parents and make lookup tables; the main thing we do here is manage the mutationEffect() callbacks @@ -8173,13 +8152,17 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit const Polymorphism &polymorphism = polymorphism_pair.second; const Mutation *mutation_ptr = polymorphism.mutation_ptr_; const MutationType *mutation_type_ptr = mutation_ptr->mutation_type_ptr_; + const MutationTraitInfo *mut_trait_info = mutation_block_->TraitInfoForMutation(mutation_ptr); slim_polymorphismid_t polymorphism_id = polymorphism.polymorphism_id_; int64_t mutation_id = mutation_ptr->mutation_id_; // Added in version 2 slim_objectid_t mutation_type_id = mutation_type_ptr->mutation_type_id_; slim_position_t position = mutation_ptr->position_; - slim_effect_t selection_coeff = mutation_ptr->selection_coeff_; - slim_effect_t dominance_coeff = mutation_ptr->dominance_coeff_; + + // FIXME MULTITRAIT: for now we just write out trait 0, need to write out all of them with a count... + slim_effect_t selection_coeff = mut_trait_info->effect_size_; + slim_effect_t dominance_coeff = mut_trait_info->dominance_coeff_; + // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved; too edge to be bothered... slim_objectid_t subpop_index = mutation_ptr->subpop_index_; slim_tick_t origin_tick = mutation_ptr->origin_tick_; @@ -8335,8 +8318,11 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit int64_t mutation_id = substitution_ptr->mutation_id_; slim_objectid_t mutation_type_id = mutation_type_ptr->mutation_type_id_; slim_position_t position = substitution_ptr->position_; - slim_effect_t selection_coeff = substitution_ptr->selection_coeff_; - slim_effect_t dominance_coeff = substitution_ptr->dominance_coeff_; + + // FIXME MULTITRAIT: for now we just write out trait 0, need to write out all of them with a count... + slim_effect_t selection_coeff = substitution_ptr->trait_info_[0].effect_size_; + slim_effect_t dominance_coeff = substitution_ptr->trait_info_[0].dominance_coeff_; + slim_objectid_t subpop_index = substitution_ptr->subpop_index_; slim_tick_t origin_tick = substitution_ptr->origin_tick_; slim_tick_t fixation_tick = substitution_ptr->fixation_tick_; diff --git a/core/population.h b/core/population.h index cf9a5f42..1d69fa45 100644 --- a/core/population.h +++ b/core/population.h @@ -220,9 +220,6 @@ class Population Individual *(Subpopulation::*GenerateIndividualSelfed_TEMPLATED)(Individual *p_parent) = nullptr; Individual *(Subpopulation::*GenerateIndividualCloned_TEMPLATED)(Individual *p_parent) = nullptr; - // An internal method that validates cached fitness values kept by Mutation objects - void ValidateMutationFitnessCaches(void); - // Recalculate all fitness values for the parental generation, including the use of mutationEffect() callbacks void RecalculateFitness(slim_tick_t p_tick); diff --git a/core/slim_functions.cpp b/core/slim_functions.cpp index b639665b..408df42a 100644 --- a/core/slim_functions.cpp +++ b/core/slim_functions.cpp @@ -665,6 +665,9 @@ R"V0G0N({ return theta; })V0G0N"; +// FIXME MULTITRAIT: changed selectionCoeff to effect in gSLiMSourceCode_calcInbreedingLoad, but really this +// needs to somehow be adapted for multitrait models; sum across all multiplicative effects; what about +// additive effects? exclude them, or raise an error? allow the user to pass a vector of traits here? #pragma mark (float$)calcInbreedingLoad(object haplosomes, [Nio$ mutType = NULL]) const char *gSLiMSourceCode_calcInbreedingLoad = R"V0G0N({ @@ -702,7 +705,7 @@ R"V0G0N({ else muts = species.subsetMutations(mutType=mutType, chromosome=chromosome); - muts = muts[muts.selectionCoeff < 0.0]; + muts = muts[muts.effect < 0.0]; // get frequencies and focus on those that are in the haplosomes q = haplosomes.mutationFrequenciesInHaplosomes(muts); @@ -713,7 +716,7 @@ R"V0G0N({ // fetch selection coefficients; note that we use the negation of // SLiM's selection coefficient, following Morton et al. 1956's usage - s = -muts.selectionCoeff; + s = -muts.effect; // replace s > 1.0 with s == 1.0; a mutation can't be more lethal // than lethal (this can happen when drawing from a gamma distribution) diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 03c720d4..5303c9cc 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -128,8 +128,8 @@ void _RunMutationTypeTests(void) // Test MutationType - (float)drawSelectionCoefficient([integer$ n = 1]) // the parameters here are chosen so that these tests should fail extremely rarely - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (m1.drawEffectForTrait() == 2.2) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (identical(m1.drawEffectForTrait(NULL, 10), rep(2.2, 10))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (abs(m1.drawEffectForTrait() - 2.2) < 1e-6) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (all(abs(m1.drawEffectForTrait(NULL, 10) - rep(2.2, 10)) < 1e-6)) stop(); }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 3.1) < 0.1) stop(); }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3.0); m1.drawEffectForTrait(); }", __LINE__); @@ -683,13 +683,13 @@ void _RunSubstitutionTests(void) SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; if (sub.mutationType == m1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; if (sub.originTick > 0 & sub.originTick <= 10) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; if (sub.position >= 0 & sub.position <= 99999) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { if (sum(sim.substitutions.selectionCoeff == 500.0) == 1) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { if (sum(sim.substitutions.effect == 500.0) == 1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; if (sub.subpopID == 1) stop(); }", __LINE__); SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.fixationTick = 10; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.mutationType = m1; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.originTick = 10; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.position = 99999; stop(); }", "read-only property", __LINE__); - SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.selectionCoeff = 50.0; stop(); }", "read-only property", __LINE__); + SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.effect = 50.0; stop(); }", "read-only property", __LINE__); SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.subpopID = 237; if (sub.subpopID == 237) stop(); }", __LINE__); // legal; this field may be used as a user tag } diff --git a/core/species.cpp b/core/species.cpp index a6da178f..747558e6 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -7771,7 +7771,16 @@ void Species::MetadataForMutation(Mutation *p_mutation, MutationMetadataRec *p_m EIDOS_TERMINATION << "ERROR (Species::MetadataForMutation): (internal error) bad parameters to MetadataForMutation()." << EidosTerminate(); p_metadata->mutation_type_id_ = p_mutation->mutation_type_ptr_->mutation_type_id_; - p_metadata->selection_coeff_ = p_mutation->selection_coeff_; + + // FIXME MULTITRAIT: We need to figure out where we're going to multitrait information in .trees + // For now we just write out the effect for trait 0, but we need the dominance coeff too, and we need + // it for all traits in the model not just trait 0; this design is not going to work. See + // https://github.com/MesserLab/SLiM/issues/569 + MutationBlock *mutation_block = p_mutation->mutation_type_ptr_->mutation_block_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(p_mutation); + + p_metadata->selection_coeff_ = mut_trait_info[0].effect_size_; + p_metadata->subpop_index_ = p_mutation->subpop_index_; p_metadata->origin_tick_ = p_mutation->origin_tick_; p_metadata->nucleotide_ = p_mutation->nucleotide_; @@ -7785,7 +7794,13 @@ void Species::MetadataForSubstitution(Substitution *p_substitution, MutationMeta EIDOS_TERMINATION << "ERROR (Species::MetadataForSubstitution): (internal error) bad parameters to MetadataForSubstitution()." << EidosTerminate(); p_metadata->mutation_type_id_ = p_substitution->mutation_type_ptr_->mutation_type_id_; - p_metadata->selection_coeff_ = p_substitution->selection_coeff_; + + // FIXME MULTITRAIT: We need to figure out where we're going to multitrait information in .trees + // For now we just write out the effect for trait 0, but we need the dominance coeff too, and we need + // it for all traits in the model not just trait 0; this design is not going to work. See + // https://github.com/MesserLab/SLiM/issues/569 + p_metadata->selection_coeff_ = p_substitution->trait_info_[0].effect_size_; + p_metadata->subpop_index_ = p_substitution->subpop_index_; p_metadata->origin_tick_ = p_substitution->origin_tick_; p_metadata->nucleotide_ = p_substitution->nucleotide_; diff --git a/core/species.h b/core/species.h index 3281b306..d0007c73 100644 --- a/core/species.h +++ b/core/species.h @@ -403,9 +403,6 @@ class Species : public EidosDictionaryUnretained int32_t nonneutral_change_counter_ = 0; int32_t last_nonneutral_regime_ = 0; // see mutation_run.h; 1 = no mutationEffect() callbacks, 2 = only constant-effect neutral callbacks, 3 = arbitrary callbacks - // this flag is set if the dominance coeff (regular or haploid) changes on any mutation type, as a signal that recaching needs to occur in Subpopulation::UpdateFitness() - bool any_dominance_coeff_changed_ = false; - // state about what symbols/names/identifiers have been used or are being used // used_subpop_ids_ has every subpop id ever used, even if no longer in use, with the *last* name used for that subpop // used_subpop_names_ has every name ever used EXCEPT standard p1, p2... names, even if the name got replaced by a new name later diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 2f6457f9..25ae223d 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1379,15 +1379,6 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect { const std::map &mut_types = species_.MutationTypes(); - // The FitnessOfParent...() methods called by this method rely upon cached fitness values - // kept inside the Mutation objects. Those caches may need to be validated before we can - // calculate fitness values. We check for that condition and repair it first. - if (species_.any_dominance_coeff_changed_) - { - population_.ValidateMutationFitnessCaches(); // note one subpop triggers it, but the recaching occurs for the whole sim - species_.any_dominance_coeff_changed_ = false; - } - // This function calculates the population mean fitness as a side effect double totalFitness = 0.0; @@ -2959,7 +2950,8 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom single_callback_mut_type = species_.MutationTypeWithID(mutation_type_id); } - Mutation *mut_block_ptr = species_.SpeciesMutationBlock()->mutation_buffer_; + MutationBlock *mutation_block = species_.SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; if (haplosome1_null && haplosome2_null) { @@ -2994,17 +2986,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { MutationIndex haplosome_mutindex = *haplosome_iter++; Mutation *mutation = mut_block_ptr + haplosome_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_hemizygousdom_sel = mutation_block->TraitInfoForIndex(haplosome_mutindex)[0].hemizygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome_mutindex, -1, mutation->cached_one_plus_hemizygousdom_sel_, p_mutationEffect_callbacks, haplosome->individual_); + w *= ApplyMutationEffectCallbacks(haplosome_mutindex, -1, cached_one_plus_hemizygousdom_sel, p_mutationEffect_callbacks, haplosome->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_hemizygousdom_sel_; + w *= cached_one_plus_hemizygousdom_sel; } } } @@ -3048,17 +3042,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { // Process a mutation in haplosome1 since it is leading Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } if (++haplosome1_iter == haplosome1_max) @@ -3072,17 +3068,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { // Process a mutation in haplosome2 since it is leading Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } if (++haplosome2_iter == haplosome2_max) @@ -3110,17 +3108,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { // a match was found, so we multiply our fitness by the full selection coefficient Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].homozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, true, mutation->cached_one_plus_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, true, cached_one_plus_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_sel_; + w *= cached_one_plus_sel; } goto homozygousExit1; } @@ -3131,17 +3131,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient { Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } } @@ -3175,17 +3177,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient { Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } } @@ -3216,17 +3220,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { MutationIndex haplosome1_mutindex = *haplosome1_iter++; Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } } @@ -3235,17 +3241,19 @@ double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosom { MutationIndex haplosome2_mutindex = *haplosome2_iter++; Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[0].heterozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, mutation->cached_one_plus_dom_sel_, p_mutationEffect_callbacks, haplosome1->individual_); + w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_dom_sel_; + w *= cached_one_plus_dom_sel; } } } @@ -3286,7 +3294,8 @@ double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vect single_callback_mut_type = species_.MutationTypeWithID(mutation_type_id); } - Mutation *mut_block_ptr = species_.SpeciesMutationBlock()->mutation_buffer_; + MutationBlock *mutation_block = species_.SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; const int32_t mutrun_count = haplosome->mutrun_count_; double w = 1.0; @@ -3310,17 +3319,19 @@ double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vect { MutationIndex haplosome_mutation = *haplosome_iter++; Mutation *mutation = (mut_block_ptr + haplosome_mutation); + // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! + slim_effect_t cached_one_plus_sel = mutation_block->TraitInfoForIndex(haplosome_mutation)[0].homozygous_effect_; if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - w *= ApplyMutationEffectCallbacks(haplosome_mutation, -1, mutation->cached_one_plus_sel_, p_mutationEffect_callbacks, haplosome->individual_); + w *= ApplyMutationEffectCallbacks(haplosome_mutation, -1, cached_one_plus_sel, p_mutationEffect_callbacks, haplosome->individual_); if (w <= 0.0) return 0.0; } else { - w *= mutation->cached_one_plus_sel_; + w *= cached_one_plus_sel; } } } diff --git a/core/substitution.cpp b/core/substitution.cpp index 6eaa8b4a..20dd0942 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -36,7 +36,7 @@ #pragma mark - Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : - EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), selection_coeff_(p_mutation.selection_coeff_), dominance_coeff_(p_mutation.dominance_coeff_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) + EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) { AddKeysAndValuesFrom(&p_mutation); @@ -45,8 +45,7 @@ Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : // Copy per-trait information over from the mutation object Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationIndex mut_index = mutation_block->IndexInBlock(&p_mutation); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoIndex(mut_index); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(&p_mutation); int trait_count = species.TraitCount(); trait_info_ = (SubstitutionTraitInfo *)malloc(trait_count * sizeof(SubstitutionTraitInfo)); @@ -59,7 +58,7 @@ Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : } Substitution::Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide) : -mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_(static_cast(p_selection_coeff)), dominance_coeff_(static_cast(p_dominance_coeff)), subpop_index_(p_subpop_index), origin_tick_(p_tick), fixation_tick_(p_fixation_tick), chromosome_index_(p_chromosome_index), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id), tag_value_(SLIM_TAG_UNSET_VALUE) +mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_subpop_index), origin_tick_(p_tick), fixation_tick_(p_fixation_tick), chromosome_index_(p_chromosome_index), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id), tag_value_(SLIM_TAG_UNSET_VALUE) { // FIXME MULTITRAIT: This code path is hit when loading substitutions from an output file, also needs to initialize the multitrait info; this is just a // placeholder. The file being read in ought to specify per-trait values, which hasn't happened yet, so there are lots of details to be worked out... @@ -68,7 +67,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), selection_coeff_ trait_info_ = (SubstitutionTraitInfo *)malloc(trait_count * sizeof(SubstitutionTraitInfo)); - for (int trait_index = 0; trait_index < trait_count; trait_index++) + trait_info_[0].effect_size_ = p_selection_coeff; + trait_info_[0].dominance_coeff_ = p_dominance_coeff; + + for (int trait_index = 1; trait_index < trait_count; trait_index++) { trait_info_[trait_index].effect_size_ = 0.0; trait_info_[trait_index].dominance_coeff_ = 0.0; @@ -91,8 +93,15 @@ void Substitution::PrintForSLiMOutput(std::ostream &p_out) const p_out << " \"" << chromosome->Symbol() << "\""; } + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; + // and then the remainder of the output line - p_out << " " << selection_coeff_ << " " << dominance_coeff_ << " p" << subpop_index_ << " " << origin_tick_ << " "<< fixation_tick_; + p_out << " p" << subpop_index_ << " " << origin_tick_ << " " << fixation_tick_; // output a nucleotide if available if (mutation_type_ptr_->nucleotide_based_) @@ -119,8 +128,15 @@ void Substitution::PrintForSLiMOutput_Tag(std::ostream &p_out) const p_out << " \"" << chromosome->Symbol() << "\""; } + // write out per-trait information + // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here + int trait_count = species.TraitCount(); + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; + // and then the remainder of the output line - p_out << " " << selection_coeff_ << " " << dominance_coeff_ << " p" << subpop_index_ << " " << origin_tick_ << " "<< fixation_tick_; + p_out << " p" << subpop_index_ << " " << origin_tick_ << " " << fixation_tick_; // output a nucleotide if available if (mutation_type_ptr_->nucleotide_based_) @@ -150,7 +166,8 @@ const EidosClass *Substitution::Class(void) const void Substitution::Print(std::ostream &p_ostream) const { - p_ostream << Class()->ClassNameForDisplay() << "<" << mutation_id_ << ":" << selection_coeff_ << ">"; + // BCH 10/19/2025: Changing from selection_coeff_ to position_ here, as part of multitrait work + p_ostream << Class()->ClassNameForDisplay() << "<" << mutation_id_ << ":" << position_ << ">"; } EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) @@ -182,7 +199,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selection_coeff_)); // FIXME MULTITRAIT + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[0].effect_size_)); else if (trait_count == 0) return gStaticEidosValue_Float_ZeroVec; else @@ -208,7 +225,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance_coeff_)); // FIXME MULTITRAIT + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[0].dominance_coeff_)); else if (trait_count == 0) return gStaticEidosValue_Float_ZeroVec; else diff --git a/core/substitution.h b/core/substitution.h index 5bbf6f6b..8306d442 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -61,8 +61,6 @@ class Substitution : public EidosDictionaryRetained MutationType *mutation_type_ptr_; // mutation type identifier slim_position_t position_; // position - slim_effect_t selection_coeff_; // selection coefficient (s) - slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default slim_objectid_t subpop_index_; // subpopulation in which mutation arose slim_tick_t origin_tick_; // tick in which mutation arose slim_tick_t fixation_tick_; // tick in which mutation fixed From 5891bbac1f1b293d4958c1ef22814b80f12970fa Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 25 Oct 2025 22:30:34 -0400 Subject: [PATCH 024/107] add phenotypeForTrait() --- QtSLiM/help/SLiMHelpClasses.html | 2 ++ SLiMgui/SLiMHelpClasses.rtf | 16 +++++++++ VERSIONS | 1 + core/individual.cpp | 58 ++++++++++++++++++++++++++------ core/individual.h | 11 +++--- core/slim_globals.cpp | 1 + core/slim_globals.h | 2 ++ core/slim_test_genetics.cpp | 14 ++++++++ 8 files changed, 89 insertions(+), 16 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index fe3af8c7..be16aa3c 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -468,6 +468,8 @@

This method replaces the deprecated method uniqueMutationsOfType(), while providing additional useful options.  It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.

– (float)offsetForTrait([Nio<Trait> trait = NULL])

Returns the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)phenotypeForTrait([Nio<Trait> trait = NULL])

+

Returns the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+ (void)outputIndividuals([Ns$ filePath = NULL], [logical$ append = F], [Niso<Chromosome>$ chromosome = NULL], [logical$ spatialPositions = T], [logical$ ages = T], [logical$ ancestralNucleotides = F], [logical$ pedigreeIDs = F], [logical$ objectTags = F])

Output the state of the target vector of individuals in SLiM's own format.  If the optional parameter filePath is NULL (the default), output will be sent to Eidos’s output stream.  Otherwise, output will be sent to the filesystem path specified by filePath, overwriting that file if append if F, or appending to the end of it if append is T.  This method is quite similar to the Species method outputFull(), but (1) it can produce output for any vector of individuals, not always for the entire population; (2) it does not support output in a binary format; (3) it can produce output regarding the genetics for all chromosomes or for just one focal chromosome; and (4) there is no corresponding read method, as readFromPopulationFile() can read the data saved by outputFull().

The chromosome parameter specifies a focal chromosome for which the genetics of the target individuals will be output.  If chromosome is NULL, all chromosomes will be output; otherwise, chromosome may specify the focal chromosome with an integer chromosome id, a string chromosome symbol, or a Chromosome object.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 828af7d7..0fa896c7 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -3813,6 +3813,22 @@ This method replaces the deprecated method \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(float)phenotypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the individual phenotype(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 +\'a0(void)outputIndividuals([Ns$\'a0filePath\'a0=\'a0NULL], [logical$\'a0append\'a0=\'a0F], [Niso$\'a0chromosome\'a0=\'a0NULL], [logical$\'a0spatialPositions\'a0=\'a0T], [logical$\'a0ages\'a0=\'a0T], [logical$\'a0ancestralNucleotides\'a0=\'a0F], [logical$\'a0pedigreeIDs\'a0=\'a0F], [logical$\'a0objectTags\'a0=\'a0F])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 diff --git a/VERSIONS b/VERSIONS index c8cb25be..bcb25b4b 100644 --- a/VERSIONS +++ b/VERSIONS @@ -66,6 +66,7 @@ multitrait branch: remove Mutation method setSelectionCoeff(), autofixing to setEffectForTrait(NULL, ) rename the selectionCoeff property to effect, for both Mutation and Substitution; it changes from float$ to float, and now returns all trait effects; and SLiMgui autofixes this change remove the old C++ selection_coeff_ and dominance_coeff_ ivars in Mutation and Substitution, and begin the transition over to the new MutationTraitInfo struct + add Individual method -(float)phenotypeForTrait([Nio traits = NULL]) to get trait values version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index 1698ba7c..02207e8c 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -145,7 +145,7 @@ void Individual::_InitializePerTraitInformation(void) #endif trait_info_ = &trait_info_0_; - trait_info_0_.value_ = 0.0; + trait_info_0_.phenotype_ = 0.0; trait_info_0_.offset_ = traits[0]->DrawIndividualOffset(); } else if (trait_count == 0) @@ -174,11 +174,11 @@ void Individual::_InitializePerTraitInformation(void) #endif if (!trait_info_) - trait_info_ = static_cast(malloc(trait_count * sizeof(SLiM_PerTraitInfo))); + trait_info_ = static_cast(malloc(trait_count * sizeof(IndividualTraitInfo))); for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - trait_info_[trait_index].value_ = 0.0; + trait_info_[trait_index].phenotype_ = 0.0; trait_info_[trait_index].offset_ = traits[trait_index]->DrawIndividualOffset(); } } @@ -1761,7 +1761,7 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) if (trait) { - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].value_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].phenotype_)); } return super::GetProperty(p_property_id); @@ -2515,7 +2515,7 @@ EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID { const Individual *value = individuals_buffer[value_index]; - float_result->set_float_no_check(value->trait_info_[trait_index].value_, value_index); + float_result->set_float_no_check(value->trait_info_[trait_index].phenotype_, value_index); } } else @@ -2527,7 +2527,7 @@ EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); int64_t trait_index = trait->Index(); - float_result->set_float_no_check(value->trait_info_[trait_index].value_, value_index); + float_result->set_float_no_check(value->trait_info_[trait_index].phenotype_, value_index); } } @@ -2659,7 +2659,7 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue if (trait) // ACCELERATED { - trait_info_[trait->Index()].value_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + trait_info_[trait->Index()].phenotype_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); return; } @@ -3087,7 +3087,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope { const Individual *value = individuals_buffer[value_index]; - value->trait_info_[trait_index].value_ = source_value; + value->trait_info_[trait_index].phenotype_ = source_value; } } else @@ -3096,7 +3096,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope { const Individual *value = individuals_buffer[value_index]; - value->trait_info_[trait_index].value_ = (slim_effect_t)source_data[value_index]; + value->trait_info_[trait_index].phenotype_ = (slim_effect_t)source_data[value_index]; } } } @@ -3113,7 +3113,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); int64_t trait_index = trait->Index(); - value->trait_info_[trait_index].value_ = source_value; + value->trait_info_[trait_index].phenotype_ = source_value; } } else @@ -3124,7 +3124,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); int64_t trait_index = trait->Index(); - value->trait_info_[trait_index].value_ = (slim_effect_t)source_data[value_index]; + value->trait_info_[trait_index].phenotype_ = (slim_effect_t)source_data[value_index]; } } } @@ -3138,6 +3138,7 @@ EidosValue_SP Individual::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, //case gID_countOfMutationsOfType: return ExecuteMethod_Accelerated_countOfMutationsOfType(p_method_id, p_arguments, p_interpreter); case gID_haplosomesForChromosomes: return ExecuteMethod_haplosomesForChromosomes(p_method_id, p_arguments, p_interpreter); case gID_offsetForTrait: return ExecuteMethod_offsetForTrait(p_method_id, p_arguments, p_interpreter); + case gID_phenotypeForTrait: return ExecuteMethod_phenotypeForTrait(p_method_id, p_arguments, p_interpreter); case gID_relatedness: return ExecuteMethod_relatedness(p_method_id, p_arguments, p_interpreter); case gID_sharedParentCount: return ExecuteMethod_sharedParentCount(p_method_id, p_arguments, p_interpreter); //case gID_sumOfMutationsOfType: return ExecuteMethod_Accelerated_sumOfMutationsOfType(p_method_id, p_arguments, p_interpreter); @@ -3353,6 +3354,40 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met } } +// ********************* - (float)phenotypeForTrait([Nio trait = NULL]) +// +EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = subpopulation_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "phenotypeForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t phenotype = trait_info_[trait_index].phenotype_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(phenotype)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t phenotype = trait_info_[trait_index].phenotype_; + + float_result->push_float_no_check(phenotype); + } + + return EidosValue_SP(float_result); + } +} + // ********************* - (float)relatedness(object individuals, [Niso$ chromosome = NULL]) // EidosValue_SP Individual::ExecuteMethod_relatedness(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -4209,6 +4244,7 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_relatedness, kEidosValueMaskFloat))->AddObject("individuals", gSLiM_Individual_Class)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, "chromosome", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_haplosomesForChromosomes, kEidosValueMaskObject, gSLiM_Haplosome_Class))->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddInt_OSN("index", gStaticEidosValueNULL)->AddLogical_OS("includeNulls", gStaticEidosValue_LogicalT)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_phenotypeForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); diff --git a/core/individual.h b/core/individual.h index ec3d0b47..11353291 100644 --- a/core/individual.h +++ b/core/individual.h @@ -65,11 +65,11 @@ inline slim_pedigreeid_t SLiM_GetNextPedigreeID_Block(int p_block_size) // This struct contains all information for a single trait in a single individual. In a multitrait // model, each individual has a pointer to a buffer of these records, providing per-trait information. -typedef struct _SLiM_PerTraitInfo +typedef struct _IndividualTraitInfo { - slim_effect_t value_; // the phenotypic value for a trait + slim_effect_t phenotype_; // the phenotypic value for a trait slim_effect_t offset_; // the individual offset combined in to produce a trait value -} SLiM_PerTraitInfo; +} IndividualTraitInfo; class Individual : public EidosDictionaryUnretained { @@ -161,8 +161,8 @@ class Individual : public EidosDictionaryUnretained // Per-trait information: trait offsets, trait values. If the species has 0 traits, the pointer is // nullptr; if 1 trait, it points to trait_info_0_ for memory locality and to avoid mallocs; if 2+ // trait, it points to an OWNED malloced buffer. - SLiM_PerTraitInfo trait_info_0_; - SLiM_PerTraitInfo *trait_info_; + IndividualTraitInfo trait_info_0_; + IndividualTraitInfo *trait_info_; // Continuous space ivars. These are effectively free tag values of type float, unless they are used by interactions. double spatial_x_, spatial_y_, spatial_z_; @@ -340,6 +340,7 @@ class Individual : public EidosDictionaryUnretained static EidosValue_SP ExecuteMethod_Accelerated_countOfMutationsOfType(EidosObject **p_values, size_t p_values_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_haplosomesForChromosomes(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_offsetForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_relatedness(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_sharedParentCount(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); static EidosValue_SP ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosObject **p_values, size_t p_values_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 516afb61..c676d22b 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1358,6 +1358,7 @@ const std::string &gStr_positionsOfMutationsOfType = EidosRegisteredString("posi const std::string &gStr_containsMarkerMutation = EidosRegisteredString("containsMarkerMutation", gID_containsMarkerMutation); const std::string &gStr_haplosomesForChromosomes = EidosRegisteredString("haplosomesForChromosomes", gID_haplosomesForChromosomes); const std::string &gStr_offsetForTrait = EidosRegisteredString("offsetForTrait", gID_offsetForTrait); +const std::string &gStr_phenotypeForTrait = EidosRegisteredString("phenotypeForTrait", gID_phenotypeForTrait); const std::string &gStr_setOffsetForTrait = EidosRegisteredString("setOffsetForTrait", gID_setOffsetForTrait); const std::string &gStr_relatedness = EidosRegisteredString("relatedness", gID_relatedness); const std::string &gStr_sharedParentCount = EidosRegisteredString("sharedParentCount", gID_sharedParentCount); diff --git a/core/slim_globals.h b/core/slim_globals.h index 412f67b3..8aa3b4f4 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -948,6 +948,7 @@ extern const std::string &gStr_positionsOfMutationsOfType; extern const std::string &gStr_containsMarkerMutation; extern const std::string &gStr_haplosomesForChromosomes; extern const std::string &gStr_offsetForTrait; +extern const std::string &gStr_phenotypeForTrait; extern const std::string &gStr_setOffsetForTrait; extern const std::string &gStr_relatedness; extern const std::string &gStr_sharedParentCount; @@ -1426,6 +1427,7 @@ enum _SLiMGlobalStringID : int { gID_containsMarkerMutation, gID_haplosomesForChromosomes, gID_offsetForTrait, + gID_phenotypeForTrait, gID_setOffsetForTrait, gID_relatedness, gID_sharedParentCount, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 5303c9cc..761c731e 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1068,6 +1068,20 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_height), rep(3.5, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_weight), rep(2.5, 5))) stop(); }"); + // individual phenotype + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.phenotypeForTrait(T_height), p1.individuals.height)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.phenotypeForTrait(T_weight), p1.individuals.weight)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.phenotypeForTrait(NULL), asVector(cbind(p1.individuals.height, p1.individuals.weight)))) stop(); }"); + +// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, 3); p1.individuals.setOffsetForTrait(1, 4.5); if (!identical(p1.individuals.offsetForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); +// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, 1:5 * 2 - 1); p1.individuals.setOffsetForTrait(1, 1:5 * 2); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); +// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(NULL, 1:10); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); +// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(NULL, 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(NULL), 1:10 + 0.5)) stop(); }"); +// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(0,1), 1:10); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); +// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(0,1), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(NULL), 1:10 + 0.5)) stop(); }"); +// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1.0:10)) stop(); }"); +// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + // species trait property access SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(sim.height, sim.traitsWithNames('height'))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(sim.weight, sim.traitsWithNames('weight'))) stop(); }"); From 22b5ecf32db12168460f5b0f56da57d3fa60164b Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 25 Oct 2025 23:02:22 -0400 Subject: [PATCH 025/107] add setPhenotypeForTrait() method to Individual --- QtSLiM/help/SLiMHelpClasses.html | 3 + SLiMgui/SLiMHelpClasses.rtf | 27 +++++++ VERSIONS | 1 + core/individual.cpp | 129 +++++++++++++++++++++++++++++++ core/individual.h | 1 + core/slim_globals.cpp | 1 + core/slim_globals.h | 2 + core/slim_test_genetics.cpp | 16 ++-- 8 files changed, 172 insertions(+), 8 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index be16aa3c..b7f7a271 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -500,6 +500,9 @@

+ (void)setOffsetForTrait([Nio<Trait> trait = NULL], [Nif offset = NULL])

Sets the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter offset must follow one of four patterns.  In the first pattern, offset is NULL; this draws the offset for each of the specified traits from each trait’s individual-offset distribution (defined by each trait’s individualOffsetMean and individualOffsetSD properties) in each target individual.  (Note that individual offsets are automatically drawn from these distributions when an individual is created; this re-draws new offset values.)  In the second pattern, offset is a singleton value; this sets the given offset for each of the specified traits in each target individual.  In the third pattern, offset is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual.  In the fourth pattern, offset is of length equal to the number of specified traits times the number of target individuals; this uses offset to provide a different offset value for each trait in each individual, using consecutive values from offset to set the offset for each of the specified traits in one individual before moving to the next individual.

+

+ (void)setPhenotypeForTrait(Nio<Trait> trait, numeric phenotype)

+

Sets the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

The parameter phenotype must follow one of three patterns.  In the first pattern, phenotype is a singleton value; this sets the given phenotype for each of the specified traits in each target individual.  In the second pattern, phenotype is of length equal to the number of specified traits; this sets the phenotype for each of the specified traits to the corresponding phenotype in each target individual.  In the third pattern, phenotype is of length equal to the number of specified traits times the number of target individuals; this uses phenotype to provide a different phenotype for each trait in each individual, using consecutive values from phenotype to set the phenotype for each of the specified traits in one individual before moving to the next individual.

+ (void)setSpatialPosition(float position)

Sets the spatial position of the individual (as accessed through the spatialPosition property).  The length of position (the number of coordinates in the spatial position of an individual) depends upon the spatial dimensionality declared with initializeSLiMOptions().  If the spatial dimensionality is zero (as it is by default), it is an error to call this method.  The elements of position are set into the values of the x, y, and z properties (if those properties are encompassed by the spatial dimensionality of the simulation).  In other words, if the declared dimensionality is "xy", calling individual.setSpatialPosition(c(1.0, 0.5)) property is equivalent to individual.x = 1.0; individual.y = 0.5; individual.z is not set (even if a third value is supplied in position) since it is not encompassed by the simulation’s dimensionality in this example.

Note that this is an Eidos class method, somewhat unusually, which allows it to work in a special way when called on a vector of individuals.  When the target vector of individuals is non-singleton, this method can do one of two things.  If position contains just a single point (i.e., is equal in length to the spatial dimensionality of the model), the spatial position of all of the target individuals will be set to the given point.  Alternatively, if position contains one point per target individual (i.e., is equal in length to the number of individuals multiplied by the spatial dimensionality of the model), the spatial position of each target individual will be set to the corresponding point from position (where the point data is concatenated, not interleaved, just as it would be returned by accessing the spatialPosition property on the vector of target individuals).  Calling this method with a position vector of any other length is an error.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 0fa896c7..8d14126c 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -4222,6 +4222,33 @@ The parameter \f4\fs20 to set the offset for each of the specified traits in one individual before moving to the next individual.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 +\'a0(void)setPhenotypeForTrait(Nio\'a0trait, numeric\'a0phenotype)\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Sets the individual phenotype(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 phenotype +\f4\fs20 must follow one of three patterns. In the first pattern, +\f3\fs18 phenotype +\f4\fs20 is a singleton value; this sets the given phenotype for each of the specified traits in each target individual. In the second pattern, +\f3\fs18 phenotype +\f4\fs20 is of length equal to the number of specified traits; this sets the phenotype for each of the specified traits to the corresponding phenotype in each target individual. In the third pattern, +\f3\fs18 phenotype +\f4\fs20 is of length equal to the number of specified traits times the number of target individuals; this uses +\f3\fs18 phenotype +\f4\fs20 to provide a different phenotype for each trait in each individual, using consecutive values from +\f3\fs18 phenotype +\f4\fs20 to set the phenotype for each of the specified traits in one individual before moving to the next individual.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \expnd0\expndtw0\kerning0 +\'a0(void)setSpatialPosition(float\'a0position)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 diff --git a/VERSIONS b/VERSIONS index bcb25b4b..b5cce48c 100644 --- a/VERSIONS +++ b/VERSIONS @@ -67,6 +67,7 @@ multitrait branch: rename the selectionCoeff property to effect, for both Mutation and Substitution; it changes from float$ to float, and now returns all trait effects; and SLiMgui autofixes this change remove the old C++ selection_coeff_ and dominance_coeff_ ivars in Mutation and Substitution, and begin the transition over to the new MutationTraitInfo struct add Individual method -(float)phenotypeForTrait([Nio traits = NULL]) to get trait values + add Individual method +(void)setPhenotypeForTrait([Nio trait = NULL], [Nif phenotype = NULL]) to set trait values version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index 02207e8c..a4adf198 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -4246,6 +4246,7 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_phenotypeForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setPhenotypeForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddNumeric("phenotype")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_uniqueMutationsOfType, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->MarkDeprecated()); @@ -4267,6 +4268,7 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ switch (p_method_id) { case gID_setOffsetForTrait: return ExecuteMethod_setOffsetForTrait(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setPhenotypeForTrait: return ExecuteMethod_setPhenotypeForTrait(p_method_id, p_target, p_arguments, p_interpreter); case gID_outputIndividuals: return ExecuteMethod_outputIndividuals(p_method_id, p_target, p_arguments, p_interpreter); case gID_outputIndividualsToVCF: return ExecuteMethod_outputIndividualsToVCF(p_method_id, p_target, p_arguments, p_interpreter); case gID_readIndividualsFromVCF: return ExecuteMethod_readIndividualsFromVCF(p_method_id, p_target, p_arguments, p_interpreter); @@ -4426,6 +4428,133 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin return gStaticEidosValueVOID; } +// ********************* + (void)setPhenotypeForTrait([Nio trait = NULL], [Nif phenotype = NULL]) +// +EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *phenotype_value = p_arguments[1].get(); + + int individuals_count = p_target->Count(); + int phenotype_count = phenotype_value->Count(); + + if (individuals_count == 0) + return gStaticEidosValueVOID; + + Individual **individuals_buffer = (Individual **)p_target->ObjectData(); + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForIndividuals(p_target); + + if (!species) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires that all individuals belong to the same species." << EidosTerminate(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setPhenotypeForTrait"); + int trait_count = (int)trait_indices.size(); + + if (phenotype_count == 1) + { + // pattern 1: setting a single phenotype value across one or more traits in one or more individuals + slim_effect_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(0, nullptr)); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = phenotype; + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->trait_info_[trait_index].phenotype_ = phenotype; + } + } + } + else if (phenotype_count == trait_count) + { + // pattern 2: setting one phenotype value per trait, in one or more individuals + int phenotype_index = 0; + + for (int64_t trait_index : trait_indices) + { + slim_effect_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(phenotype_index++, nullptr)); + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + ind->trait_info_[trait_index].phenotype_ = phenotype; + } + } + } + else if (phenotype_count == trait_count * individuals_count) + { + // pattern 3: setting different phenotype values for each trait in each individual; in this case, + // all phenotypes for the specified traits in a given individual are given consecutively + if (phenotype_value->Type() == EidosValueType::kValueInt) + { + // integer phenotype values + const int64_t *phenotypes_int = phenotype_value->IntData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); + } + } + } + else + { + // float phenotype values + const double *phenotypes_float = phenotype_value->FloatData(); + + if (trait_count == 1) + { + // optimized case for one trait + int64_t trait_index = trait_indices[0]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_float++)); + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + ind->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_float++)); + } + } + } + } + else + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires that phenotype be (a) singleton, providing one phenotype for all traits, (b) equal in length to the number of traits in the species, providing one phenotype per trait, or (c) equal in length to the number of traits times the number of target individuals, providing one phenotype per trait per individual." << EidosTerminate(); + + return gStaticEidosValueVOID; +} + // ********************* + (void)outputIndividuals([Ns$ filePath = NULL], [logical$ append=F], [Niso$ chromosome = NULL], [logical$ spatialPositions = T], [logical$ ages = T], [logical$ ancestralNucleotides = F], [logical$ pedigreeIDs = F], [logical$ objectTags = F]) // EidosValue_SP Individual_Class::ExecuteMethod_outputIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const diff --git a/core/individual.h b/core/individual.h index 11353291..6f3d5d33 100644 --- a/core/individual.h +++ b/core/individual.h @@ -423,6 +423,7 @@ class Individual_Class : public EidosDictionaryUnretained_Class virtual EidosValue_SP ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const override; EidosValue_SP ExecuteMethod_setOffsetForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + EidosValue_SP ExecuteMethod_setPhenotypeForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_outputIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_outputIndividualsToVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_readIndividualsFromVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index c676d22b..f807e2b1 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1360,6 +1360,7 @@ const std::string &gStr_haplosomesForChromosomes = EidosRegisteredString("haplos const std::string &gStr_offsetForTrait = EidosRegisteredString("offsetForTrait", gID_offsetForTrait); const std::string &gStr_phenotypeForTrait = EidosRegisteredString("phenotypeForTrait", gID_phenotypeForTrait); const std::string &gStr_setOffsetForTrait = EidosRegisteredString("setOffsetForTrait", gID_setOffsetForTrait); +const std::string &gStr_setPhenotypeForTrait = EidosRegisteredString("setPhenotypeForTrait", gID_setPhenotypeForTrait); const std::string &gStr_relatedness = EidosRegisteredString("relatedness", gID_relatedness); const std::string &gStr_sharedParentCount = EidosRegisteredString("sharedParentCount", gID_sharedParentCount); const std::string &gStr_mutationsOfType = EidosRegisteredString("mutationsOfType", gID_mutationsOfType); diff --git a/core/slim_globals.h b/core/slim_globals.h index 8aa3b4f4..e75a37e7 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -950,6 +950,7 @@ extern const std::string &gStr_haplosomesForChromosomes; extern const std::string &gStr_offsetForTrait; extern const std::string &gStr_phenotypeForTrait; extern const std::string &gStr_setOffsetForTrait; +extern const std::string &gStr_setPhenotypeForTrait; extern const std::string &gStr_relatedness; extern const std::string &gStr_sharedParentCount; extern const std::string &gStr_mutationsOfType; @@ -1429,6 +1430,7 @@ enum _SLiMGlobalStringID : int { gID_offsetForTrait, gID_phenotypeForTrait, gID_setOffsetForTrait, + gID_setPhenotypeForTrait, gID_relatedness, gID_sharedParentCount, gID_mutationsOfType, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 761c731e..bc816e1f 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1073,14 +1073,14 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.phenotypeForTrait(T_weight), p1.individuals.weight)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.phenotypeForTrait(NULL), asVector(cbind(p1.individuals.height, p1.individuals.weight)))) stop(); }"); -// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, 3); p1.individuals.setOffsetForTrait(1, 4.5); if (!identical(p1.individuals.offsetForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); -// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, 1:5 * 2 - 1); p1.individuals.setOffsetForTrait(1, 1:5 * 2); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); -// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(NULL, 1:10); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); -// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(NULL, 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(NULL), 1:10 + 0.5)) stop(); }"); -// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(0,1), 1:10); if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); -// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(0,1), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(NULL), 1:10 + 0.5)) stop(); }"); -// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1.0:10)) stop(); }"); -// SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(0, 3); p1.individuals.setPhenotypeForTrait(1, 4.5); if (!identical(p1.individuals.phenotypeForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(0, 1:5 * 2 - 1); p1.individuals.setPhenotypeForTrait(1, 1:5 * 2); if (!identical(p1.individuals.phenotypeForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(NULL, 1:10); if (!identical(p1.individuals.phenotypeForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(NULL, 1:10 + 0.5); if (!identical(p1.individuals.phenotypeForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(c(0,1), 1:10); if (!identical(p1.individuals.phenotypeForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(c(0,1), 1:10 + 0.5); if (!identical(p1.individuals.phenotypeForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(c(1,0), 1:10); if (!identical(p1.individuals.phenotypeForTrait(c(1,0)), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(c(1,0), 1:10 + 0.5); if (!identical(p1.individuals.phenotypeForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); // species trait property access SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(sim.height, sim.traitsWithNames('height'))) stop(); }"); From d9585501a89500ed766d8b93e9581187ff52d450 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 1 Nov 2025 15:18:07 -0400 Subject: [PATCH 026/107] update the Xcode project for the PCG RNG merge --- SLiM.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SLiM.xcodeproj/project.pbxproj b/SLiM.xcodeproj/project.pbxproj index 1c3c88a2..3a6d3ea4 100644 --- a/SLiM.xcodeproj/project.pbxproj +++ b/SLiM.xcodeproj/project.pbxproj @@ -1971,6 +1971,8 @@ 986926EA1AA6B7480000E138 /* GraphView_PopulationVisualization.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GraphView_PopulationVisualization.mm; sourceTree = ""; }; 986D73E61B07E89E007FBB70 /* eidos_call_signature.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = eidos_call_signature.cpp; sourceTree = ""; }; 986D73E71B07E89E007FBB70 /* eidos_call_signature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eidos_call_signature.h; sourceTree = ""; }; + 987096582EB6937C008AFD5D /* pcg_extras.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pcg_extras.hpp; sourceTree = ""; }; + 987096592EB6937C008AFD5D /* pcg_random.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pcg_random.hpp; sourceTree = ""; }; 98729ACD2A87A93500E81662 /* eidos_sorting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = eidos_sorting.h; sourceTree = ""; }; 98729ACE2A87AAB700E81662 /* eidos_sorting.inc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.pascal; path = eidos_sorting.inc; sourceTree = ""; }; 98729AD72A87DFBE00E81662 /* eidos_sorting.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = eidos_sorting.cpp; sourceTree = ""; }; @@ -2203,8 +2205,6 @@ 98D7D6642AB24CBC002AFE34 /* chisq.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = chisq.c; sourceTree = ""; }; 98D7EBEE28CE557C00DEAAC4 /* eidos_multi */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = eidos_multi; sourceTree = BUILT_PRODUCTS_DIR; }; 98D7ED2D28CE58FC00DEAAC4 /* slim_multi */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = slim_multi; sourceTree = BUILT_PRODUCTS_DIR; }; - 98D957882EB53494008314C1 /* pcg_extras.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pcg_extras.hpp; sourceTree = ""; }; - 98D957892EB53494008314C1 /* pcg_random.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = pcg_random.hpp; sourceTree = ""; }; 98DB3D6D1E6122AE00E2C200 /* interaction_type.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = interaction_type.cpp; sourceTree = ""; }; 98DB3D6E1E6122AE00E2C200 /* interaction_type.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = interaction_type.h; sourceTree = ""; }; 98DC9838289986B300160DD8 /* GitSHA1.cpp.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GitSHA1.cpp.in; sourceTree = ""; }; @@ -2405,8 +2405,8 @@ 98235687252FDD120096A745 /* lodepng.h */, 98235681252FDCF50096A745 /* lodepng.cpp */, 98186DB8254A8B1600F9118C /* robin_hood.h */, - 98D957882EB53494008314C1 /* pcg_extras.hpp */, - 98D957892EB53494008314C1 /* pcg_random.hpp */, + 987096582EB6937C008AFD5D /* pcg_extras.hpp */, + 987096592EB6937C008AFD5D /* pcg_random.hpp */, ); name = dependencies; sourceTree = ""; From e5649a90438b6bd112a23cc9df2b4601aae5b44f Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 2 Nov 2025 14:57:02 -0500 Subject: [PATCH 027/107] initializeMutationType() sets the DES for all traits; naming shifts --- EidosScribe/EidosHelpFunctions.rtf | 7 +- PARALLEL | 2 +- QtSLiM/QtSLiMChromosomeWidget.cpp | 2 +- QtSLiM/QtSLiMChromosomeWidget_GL.cpp | 30 +-- QtSLiM/QtSLiMChromosomeWidget_QT.cpp | 30 +-- QtSLiM/QtSLiMExtras.cpp | 4 +- QtSLiM/QtSLiMExtras.h | 2 +- QtSLiM/QtSLiMHaplotypeManager.cpp | 2 +- QtSLiM/QtSLiMTablesDrawer.cpp | 68 +++--- QtSLiM/QtSLiMWindow.cpp | 9 +- QtSLiM/help/EidosHelpFunctions.html | 2 +- QtSLiM/help/SLiMHelpCallbacks.html | 4 +- QtSLiM/help/SLiMHelpClasses.html | 6 +- QtSLiM/help/SLiMHelpFunctions.html | 6 +- SLiMgui/ChromosomeView.mm | 32 +-- SLiMgui/CocoaExtra.h | 4 +- SLiMgui/CocoaExtra.mm | 8 +- SLiMgui/SLiMHelpCallbacks.rtf | 4 +- SLiMgui/SLiMHelpClasses.rtf | 8 +- SLiMgui/SLiMHelpFunctions.rtf | 29 ++- SLiMgui/SLiMWindowController.h | 2 +- SLiMgui/SLiMWindowController.mm | 68 +++--- VERSIONS | 4 +- core/genomic_element_type.cpp | 4 +- core/haplosome.cpp | 54 ++--- core/individual.cpp | 18 +- core/mutation.cpp | 16 +- core/mutation_run.h | 2 +- core/mutation_type.cpp | 327 +++++++++++++-------------- core/mutation_type.h | 46 ++-- core/polymorphism.cpp | 8 +- core/population.cpp | 16 +- core/population.h | 2 +- core/slim_test_core.cpp | 14 +- core/slim_test_genetics.cpp | 10 +- core/slim_test_other.cpp | 14 +- core/species.cpp | 20 +- core/species.h | 6 +- core/species_eidos.cpp | 36 +-- core/subpopulation.cpp | 8 +- eidos/eidos_interpreter.cpp | 16 +- eidos/eidos_test.cpp | 20 +- eidos/eidos_test_functions_other.cpp | 4 +- 43 files changed, 499 insertions(+), 475 deletions(-) diff --git a/EidosScribe/EidosHelpFunctions.rtf b/EidosScribe/EidosHelpFunctions.rtf index 622e6c94..ce604559 100644 --- a/EidosScribe/EidosHelpFunctions.rtf +++ b/EidosScribe/EidosHelpFunctions.rtf @@ -3519,8 +3519,8 @@ This is quite similar to a function in R of the same name; note, however, that E \f1\fs18 x \f3\fs20 according to which sorting should be done. This must be a simple property name; it cannot be a property path. For example, to sort a \f1\fs18 Mutation -\f3\fs20 vector by the selection coefficients of the mutations, you would simply pass -\f1\fs18 "selectionCoeff" +\f3\fs20 vector by the dominance coefficients of the mutations, you would simply pass +\f1\fs18 "dominance" \f3\fs20 , including the quotes, for \f1\fs18 property \f2\fs20 . @@ -6748,8 +6748,9 @@ Named \f1\fs18 c() \f3\fs20 function (including the possibility of type promotion).\ Since this function can be hard to understand at first, here is an example:\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\f1\fs18 sapply(1:10, "if (applyValue % 2) applyValue ^ 2; else NULL;");\ +\f1\fs18 \cf2 sapply(1:10, "if (applyValue % 2) applyValue ^ 2; else NULL;");\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f3\fs20 \cf2 \kerning1\expnd0\expndtw0 This produces the output diff --git a/PARALLEL b/PARALLEL index d31dcbf1..a5c900f2 100644 --- a/PARALLEL +++ b/PARALLEL @@ -71,7 +71,7 @@ PARALLEL changes (now in the master branch, but disabled): add support for parallel reproduction with tree-sequence recording add parallel reproduction in nonWF models, with no callbacks (recombination(), mutation()) active - modifyChild() callbacks are legal but cannot access child haplosomes since they are deferred this is achieved by passing defer=T to addCrossed(), addCloned(), addSelfed(), addMultiRecombinant(), or addRecombinant() - thread-safety work - break in backward reproducibility for scripts that use a type 's' DFE, because the code path for that shifted + thread-safety work - break in backward reproducibility for scripts that use a type 's' DES, because the code path for that shifted algorithm change for nearestNeighbors() and nearestNeighborsOfPoint(), when count is >1 and mutation_type_id_; - if (muttype->IsPureNeutralDFE()) + if (muttype->IsPureNeutralDES()) displayMuttypes_.emplace_back(muttype_id); } } diff --git a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp index e9cbc6bc..916c76fe 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp @@ -225,7 +225,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); - RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } int height_adjust = mutationTickRect.height() - static_cast(ceil((mutationRefCount / totalHaplosomeCount) * interiorRect.height())); @@ -241,9 +241,9 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch { // We have a lot of mutations, so let's try to be smarter. It's hard to be smarter. The overhead from allocating the NSColors and such // is pretty negligible; practially all the time is spent in NSRectFill(). Unfortunately, NSRectFillListWithColors() provides basically - // no speedup; Apple doesn't appear to have optimized it. So, here's what I came up with. For each mutation type that uses a fixed DFE, + // no speedup; Apple doesn't appear to have optimized it. So, here's what I came up with. For each mutation type that uses a fixed DES, // and thus a fixed color, we can do a radix sort of mutations into bins corresponding to each pixel in our displayed image. Then we - // can draw each bin just once, making one bar for the highest bar in that bin. Mutations from non-fixed DFEs, and mutations which have + // can draw each bin just once, making one bar for the highest bar in that bin. Mutations from non-fixed DESs, and mutations which have // had their selection coefficient changed, will be drawn at the end in the usual (slow) way. int displayPixelWidth = interiorRect.width(); int16_t *heightBuffer = static_cast(malloc(static_cast(displayPixelWidth) * sizeof(int16_t))); @@ -261,7 +261,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch for (auto mutationTypeIter : mut_types) { MutationType *mut_type = mutationTypeIter.second; - EffectDistributionInfo &ed_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT if (mut_type->mutation_type_displayed_) { @@ -269,14 +269,14 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch { bool mut_type_fixed_color = !mut_type->color_.empty(); - // We optimize fixed-DFE mutation types only, and those using a fixed color set by the user - if ((ed_info.dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) + // We optimize fixed-DES mutation types only, and those using a fixed color set by the user + if ((DES_info.DES_type_ == DESType::kFixed) || mut_type_fixed_color) { - slim_effect_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(ed_info.dfe_parameters_[0])); + slim_effect_t mut_type_effect = (mut_type_fixed_color ? 0.0 : static_cast(DES_info.DES_parameters_[0])); EIDOS_BZERO(heightBuffer, static_cast(displayPixelWidth) * sizeof(int16_t)); - // Scan through the mutation list for mutations of this type with the right selcoeff + // Scan through the mutation list for mutations of this type with the right effect for (int mutation_index = 0; mutation_index < (int)mutations.size(); ++mutation_index) { const Mutation *mutation = mutations[mutation_index]; @@ -285,11 +285,11 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch #pragma GCC diagnostic ignored "-Wfloat-equal" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfloat-equal" - // We do want to do an exact floating-point equality compare here; we want to see whether the mutation's selcoeff is unmodified from the fixed DFE + // We do want to do an exact floating-point equality compare here; we want to see whether the mutation's effect is unmodified from the fixed DES // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); - if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mut_trait_info[0].effect_size_ == mut_type_selcoeff))) + if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mut_trait_info[0].effect_size_ == mut_type_effect))) #pragma clang diagnostic pop #pragma GCC diagnostic pop { @@ -320,7 +320,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch } else { - RGBForSelectionCoeff(static_cast(mut_type_selcoeff), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(mut_type_effect), &colorRed, &colorGreen, &colorBlue, scalingFactor); } for (int binIndex = 0; binIndex < displayPixelWidth; ++binIndex) @@ -381,7 +381,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); - RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -437,7 +437,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); - RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -505,7 +505,7 @@ void QtSLiMChromosomeWidget::glDrawFixedSubstitutions(QRect &interiorRect, Chrom else { // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! - RGBForSelectionCoeff(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } } @@ -585,7 +585,7 @@ void QtSLiMChromosomeWidget::glDrawFixedSubstitutions(QRect &interiorRect, Chrom else { // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! - RGBForSelectionCoeff(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } mutationTickRect.setX(interiorRect.x() + binIndex); diff --git a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp index 78009f96..ffdcec04 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp @@ -221,7 +221,7 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); - RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } int height_adjust = mutationTickRect.height() - static_cast(ceil((mutationRefCount / totalHaplosomeCount) * interiorRect.height())); @@ -237,9 +237,9 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch { // We have a lot of mutations, so let's try to be smarter. It's hard to be smarter. The overhead from allocating the NSColors and such // is pretty negligible; practially all the time is spent in NSRectFill(). Unfortunately, NSRectFillListWithColors() provides basically - // no speedup; Apple doesn't appear to have optimized it. So, here's what I came up with. For each mutation type that uses a fixed DFE, + // no speedup; Apple doesn't appear to have optimized it. So, here's what I came up with. For each mutation type that uses a fixed DES, // and thus a fixed color, we can do a radix sort of mutations into bins corresponding to each pixel in our displayed image. Then we - // can draw each bin just once, making one bar for the highest bar in that bin. Mutations from non-fixed DFEs, and mutations which have + // can draw each bin just once, making one bar for the highest bar in that bin. Mutations from non-fixed DESs, and mutations which have // had their selection coefficient changed, will be drawn at the end in the usual (slow) way. int displayPixelWidth = interiorRect.width(); int16_t *heightBuffer = static_cast(malloc(static_cast(displayPixelWidth) * sizeof(int16_t))); @@ -263,16 +263,16 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch if (draw_muttypes_sequentially) { bool mut_type_fixed_color = !mut_type->color_.empty(); - EffectDistributionInfo &ed_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT - // We optimize fixed-DFE mutation types only, and those using a fixed color set by the user - if ((ed_info.dfe_type_ == DFEType::kFixed) || mut_type_fixed_color) + // We optimize fixed-DES mutation types only, and those using a fixed color set by the user + if ((DES_info.DES_type_ == DESType::kFixed) || mut_type_fixed_color) { - slim_effect_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(ed_info.dfe_parameters_[0])); + slim_effect_t mut_type_effect = (mut_type_fixed_color ? 0.0 : static_cast(DES_info.DES_parameters_[0])); EIDOS_BZERO(heightBuffer, static_cast(displayPixelWidth) * sizeof(int16_t)); - // Scan through the mutation list for mutations of this type with the right selcoeff + // Scan through the mutation list for mutations of this type with the right effect for (int mutation_index = 0; mutation_index < (int)mutations.size(); ++mutation_index) { const Mutation *mutation = mutations[mutation_index]; @@ -281,11 +281,11 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch #pragma GCC diagnostic ignored "-Wfloat-equal" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfloat-equal" - // We do want to do an exact floating-point equality compare here; we want to see whether the mutation's selcoeff is unmodified from the fixed DFE + // We do want to do an exact floating-point equality compare here; we want to see whether the mutation's effect is unmodified from the fixed DES // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); - if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mut_trait_info[0].effect_size_ == mut_type_selcoeff))) + if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mut_trait_info[0].effect_size_ == mut_type_effect))) #pragma clang diagnostic pop #pragma GCC diagnostic pop { @@ -316,7 +316,7 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch } else { - RGBForSelectionCoeff(static_cast(mut_type_selcoeff), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(mut_type_effect), &colorRed, &colorGreen, &colorBlue, scalingFactor); } for (int binIndex = 0; binIndex < displayPixelWidth; ++binIndex) @@ -377,7 +377,7 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); - RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -433,7 +433,7 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation); - RGBForSelectionCoeff(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -501,7 +501,7 @@ void QtSLiMChromosomeWidget::qtDrawFixedSubstitutions(QRect &interiorRect, Chrom else { // FIXME MULTITRAIT: should be a way to choose which trait is being used for colors in the chromosome view! - RGBForSelectionCoeff(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } } @@ -580,7 +580,7 @@ void QtSLiMChromosomeWidget::qtDrawFixedSubstitutions(QRect &interiorRect, Chrom } else { - RGBForSelectionCoeff(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } mutationTickRect.setX(interiorRect.x() + binIndex); diff --git a/QtSLiM/QtSLiMExtras.cpp b/QtSLiM/QtSLiMExtras.cpp index 4b19535f..02778b53 100644 --- a/QtSLiM/QtSLiMExtras.cpp +++ b/QtSLiM/QtSLiMExtras.cpp @@ -278,7 +278,7 @@ void RGBForFitness(double value, float *colorRed, float *colorGreen, float *colo } } -void RGBForSelectionCoeff(double value, float *colorRed, float *colorGreen, float *colorBlue, double scalingFactor) +void RGBForEffectSize(double value, float *colorRed, float *colorGreen, float *colorBlue, double scalingFactor) { // apply a scaling factor; this could be user-adjustible since different models have different relevant fitness ranges value *= scalingFactor; @@ -394,7 +394,7 @@ void QtSLiMColorScaleWidget::paintEvent(QPaintEvent * /*p_paintEvent*/) double sliverFraction = (x - (stripe2.left() + 1)) / (stripe2.width() - 3.0); double fitness = sliverFraction * 2.0 - 1; // cover mutation effect values of -1.0 to 1.0 float r, g, b; - RGBForSelectionCoeff(fitness, &r, &g, &b, scalingFactor); + RGBForEffectSize(fitness, &r, &g, &b, scalingFactor); painter.fillRect(sliver, QColor(round(r * 255), round(g * 255), round(b * 255))); //qDebug() << "x =" << x << " << sliverFraction =" << sliverFraction << " fitness =" << fitness; diff --git a/QtSLiM/QtSLiMExtras.h b/QtSLiM/QtSLiMExtras.h index dfd46dc7..adceb94e 100644 --- a/QtSLiM/QtSLiMExtras.h +++ b/QtSLiM/QtSLiMExtras.h @@ -71,7 +71,7 @@ QColor QtSLiMColorWithRGB(double p_red, double p_green, double p_blue, double p_ QColor QtSLiMColorWithHSV(double p_hue, double p_saturation, double p_value, double p_alpha); void RGBForFitness(double fitness, float *colorRed, float *colorGreen, float *colorBlue, double scalingFactor); -void RGBForSelectionCoeff(double selectionCoeff, float *colorRed, float *colorGreen, float *colorBlue, double scalingFactor); +void RGBForEffectSize(double effect, float *colorRed, float *colorGreen, float *colorBlue, double scalingFactor); // A color scale widget that shows the color scales for fitness and selection coefficients class QtSLiMColorScaleWidget : public QWidget diff --git a/QtSLiM/QtSLiMHaplotypeManager.cpp b/QtSLiM/QtSLiMHaplotypeManager.cpp index 22dee088..3f98d0f3 100644 --- a/QtSLiM/QtSLiMHaplotypeManager.cpp +++ b/QtSLiM/QtSLiMHaplotypeManager.cpp @@ -507,7 +507,7 @@ void QtSLiMHaplotypeManager::configureMutationInfoBuffer(Chromosome *chromosome) } else { - RGBForSelectionCoeff(static_cast(selection_coeff), &haplo_mut->red_, &haplo_mut->green_, &haplo_mut->blue_, scalingFactor); + RGBForEffectSize(static_cast(selection_coeff), &haplo_mut->red_, &haplo_mut->green_, &haplo_mut->blue_, scalingFactor); } haplo_mut->neutral_ = (selection_coeff == 0.0f); diff --git a/QtSLiM/QtSLiMTablesDrawer.cpp b/QtSLiM/QtSLiMTablesDrawer.cpp index 35589d15..65125786 100644 --- a/QtSLiM/QtSLiMTablesDrawer.cpp +++ b/QtSLiM/QtSLiMTablesDrawer.cpp @@ -81,15 +81,15 @@ static QImage imageForMutationOrInteractionType(MutationType *mut_type, Interact if (mut_type) { - // Generate draws for a mutation type; this case is stochastic, based upon a large number of DFE samples. + // Generate draws for a mutation type; this case is stochastic, based upon a large number of DES samples. // Draw all the values we will plot; we need our own private RNG so we don't screw up the simulation's. // Drawing selection coefficients could raise, if they are type "s" and there is an error in the script, // so we run the sampling inside a try/catch block; if we get a raise, we just show a "?" in the plot. static bool rng_initialized = false; static Eidos_RNG_State local_rng; - EffectDistributionInfo &ed_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT - sample_size = (ed_info.dfe_type_ == DFEType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast + sample_size = (DES_info.DES_type_ == DESType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast draws.reserve(sample_size); if (!rng_initialized) @@ -541,7 +541,7 @@ QVariant QtSLiMMutTypeTableModel::data(const QModelIndex &p_index, int role) con std::advance(mutTypeIter, p_index.row()); slim_objectid_t mutTypeID = mutTypeIter->first; MutationType *mutationType = mutTypeIter->second; - EffectDistributionInfo &ed_info = mutationType->effect_distributions_[0]; // FIXME MULTITRAIT + EffectDistributionInfo &DES_info = mutationType->effect_distributions_[0]; // FIXME MULTITRAIT if (p_index.column() == 0) { @@ -554,59 +554,59 @@ QVariant QtSLiMMutTypeTableModel::data(const QModelIndex &p_index, int role) con } else if (p_index.column() == 1) { - return QVariant(QString("%1").arg(static_cast(ed_info.default_dominance_coeff_), 0, 'f', 3)); + return QVariant(QString("%1").arg(static_cast(DES_info.default_dominance_coeff_), 0, 'f', 3)); } else if (p_index.column() == 2) { - switch (ed_info.dfe_type_) + switch (DES_info.DES_type_) { - case DFEType::kFixed: return QVariant(QString("fixed")); - case DFEType::kGamma: return QVariant(QString("gamma")); - case DFEType::kExponential: return QVariant(QString("exp")); - case DFEType::kNormal: return QVariant(QString("normal")); - case DFEType::kWeibull: return QVariant(QString("Weibull")); - case DFEType::kLaplace: return QVariant(QString("Laplace")); - case DFEType::kScript: return QVariant(QString("script")); + case DESType::kFixed: return QVariant(QString("fixed")); + case DESType::kGamma: return QVariant(QString("gamma")); + case DESType::kExponential: return QVariant(QString("exp")); + case DESType::kNormal: return QVariant(QString("normal")); + case DESType::kWeibull: return QVariant(QString("Weibull")); + case DESType::kLaplace: return QVariant(QString("Laplace")); + case DESType::kScript: return QVariant(QString("script")); } } else if (p_index.column() == 3) { QString paramString; - if (ed_info.dfe_type_ == DFEType::kScript) + if (DES_info.DES_type_ == DESType::kScript) { - // DFE type 's' has parameters of type string - for (unsigned int paramIndex = 0; paramIndex < ed_info.dfe_strings_.size(); ++paramIndex) + // DES type 's' has parameters of type string + for (unsigned int paramIndex = 0; paramIndex < DES_info.DES_strings_.size(); ++paramIndex) { - QString dfe_string = QString::fromStdString(ed_info.dfe_strings_[paramIndex]); + QString DES_string = QString::fromStdString(DES_info.DES_strings_[paramIndex]); - paramString += ("\"" + dfe_string + "\""); + paramString += ("\"" + DES_string + "\""); - if (paramIndex < ed_info.dfe_strings_.size() - 1) + if (paramIndex < DES_info.DES_strings_.size() - 1) paramString += ", "; } } else { - // All other DFEs have parameters of type double - for (unsigned int paramIndex = 0; paramIndex < ed_info.dfe_parameters_.size(); ++paramIndex) + // All other DESs have parameters of type double + for (unsigned int paramIndex = 0; paramIndex < DES_info.DES_parameters_.size(); ++paramIndex) { QString paramSymbol; - switch (ed_info.dfe_type_) + switch (DES_info.DES_type_) { - case DFEType::kFixed: paramSymbol = "s"; break; - case DFEType::kGamma: paramSymbol = (paramIndex == 0 ? "s̄" : "α"); break; - case DFEType::kExponential: paramSymbol = "s̄"; break; - case DFEType::kNormal: paramSymbol = (paramIndex == 0 ? "s̄" : "σ"); break; - case DFEType::kWeibull: paramSymbol = (paramIndex == 0 ? "λ" : "k"); break; - case DFEType::kLaplace: paramSymbol = (paramIndex == 0 ? "s̄" : "b"); break; - case DFEType::kScript: break; + case DESType::kFixed: paramSymbol = "s"; break; + case DESType::kGamma: paramSymbol = (paramIndex == 0 ? "s̄" : "α"); break; + case DESType::kExponential: paramSymbol = "s̄"; break; + case DESType::kNormal: paramSymbol = (paramIndex == 0 ? "s̄" : "σ"); break; + case DESType::kWeibull: paramSymbol = (paramIndex == 0 ? "λ" : "k"); break; + case DESType::kLaplace: paramSymbol = (paramIndex == 0 ? "s̄" : "b"); break; + case DESType::kScript: break; } - paramString += QString("%1=%2").arg(paramSymbol).arg(ed_info.dfe_parameters_[paramIndex], 0, 'f', 3); + paramString += QString("%1=%2").arg(paramSymbol).arg(DES_info.DES_parameters_[paramIndex], 0, 'f', 3); - if (paramIndex < ed_info.dfe_parameters_.size() - 1) + if (paramIndex < DES_info.DES_parameters_.size() - 1) paramString += ", "; } } @@ -666,7 +666,7 @@ QVariant QtSLiMMutTypeTableModel::headerData(int section, { case 0: return QVariant("ID"); case 1: return QVariant("h"); - case 2: return QVariant("DFE"); + case 2: return QVariant("DES"); case 3: return QVariant("Params"); default: return QVariant(""); } @@ -677,8 +677,8 @@ QVariant QtSLiMMutTypeTableModel::headerData(int section, { case 0: return QVariant("the ID for the mutation type"); case 1: return QVariant("the dominance coefficient"); - case 2: return QVariant("the distribution of fitness effects"); - case 3: return QVariant("the DFE parameters"); + case 2: return QVariant("the distribution of effect sizes"); + case 3: return QVariant("the DES parameters"); default: return QVariant(""); } } diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index f4aa075d..1c7b47e2 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -1913,14 +1913,15 @@ bool QtSLiMWindow::checkTerminationForAutofix(QString terminationMessage) return offerAndExecuteAutofix(entireCall, "evaluate(sim.subpopulations);", "The evaluate() method now requires a vector of subpopulations to evaluate.", terminationMessage); } - if (terminationMessage.contains("named argument immediate skipped over required argument subpops") && (selectionString == "evaluate")) + if (terminationMessage.contains("named argument 'immediate' skipped over required argument 'subpops'") && (selectionString == "evaluate")) { QTextCursor entireCall = selection; entireCall.setPosition(entireCall.selectionStart(), QTextCursor::MoveAnchor); entireCall.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 22); QString entireCallString = entireCall.selectedText(); - if ((entireCallString == "evaluate(immediate=T);") || (entireCallString == "evaluate(immediate=F);")) + if ((entireCallString == "evaluate(immediate=T);") || (entireCallString == "evaluate(immediate=F);") || + (entireCallString == "evaluate(immediate = T);") || (entireCallString == "evaluate(immediate = F);")) return offerAndExecuteAutofix(entireCall, "evaluate(sim.subpopulations);", "The evaluate() method no longer supports immediate evaluation, and the `immediate` parameter has been removed.", terminationMessage); } @@ -2167,6 +2168,10 @@ bool QtSLiMWindow::checkTerminationForAutofix(QString terminationMessage) (selectionString == "selectionCoeff")) return offerAndExecuteAutofix(selection, "effect", "The `selectionCoeff` property of Substitution has become the property `effect`.", terminationMessage); + if (terminationMessage.contains("unrecognized named argument 'selectionCoeff' to addNewMutation()") && + (selectionString == "selectionCoeff")) + return offerAndExecuteAutofix(selection, "effect", "The `selectionCoeff` parameter to addNewMutation() has been renamed to `effect`.", terminationMessage); + return false; } diff --git a/QtSLiM/help/EidosHelpFunctions.html b/QtSLiM/help/EidosHelpFunctions.html index 1af2d78b..8139ed3d 100644 --- a/QtSLiM/help/EidosHelpFunctions.html +++ b/QtSLiM/help/EidosHelpFunctions.html @@ -287,7 +287,7 @@

(+)sort(+ x, [logical$ ascending = T])

Returns a sorted copy of x: a new vector with the same elements as x, but in sorted order.  If the optional logical parameter ascending is T (the default), then the sorted order will be ascending; if it is F, the sorted order will be descending.  The ordering is determined according to the same logic as the < and > operators in Eidos.  To sort an object vector, use sortBy().  To obtain indices for sorting, use order().

(object)sortBy(object x, string$ property, [logical$ ascending = T])

-

Returns a sorted copy of x: a new vector with the same elements as x, but in sorted order.  If the optional logical parameter ascending is T (the default), then the sorted order will be ascending; if it is F, the sorted order will be descending.  The ordering is determined according to the same logic as the < and > operators in Eidos.  The property argument gives the name of the property within the elements of x according to which sorting should be done.  This must be a simple property name; it cannot be a property path.  For example, to sort a Mutation vector by the selection coefficients of the mutations, you would simply pass "selectionCoeff", including the quotes, for property.  To sort a non-object vector, use sort().  To obtain indices for sorting, use order().

+

Returns a sorted copy of x: a new vector with the same elements as x, but in sorted order.  If the optional logical parameter ascending is T (the default), then the sorted order will be ascending; if it is F, the sorted order will be descending.  The ordering is determined according to the same logic as the < and > operators in Eidos.  The property argument gives the name of the property within the elements of x according to which sorting should be done.  This must be a simple property name; it cannot be a property path.  For example, to sort a Mutation vector by the dominance coefficients of the mutations, you would simply pass "dominance", including the quotes, for property.  To sort a non-object vector, use sort().  To obtain indices for sorting, use order().

(void)str(* x, [logical$ error = F])

Prints the structure of x: a summary of its type and the values it contains.  If x is an object, note that str() produces different results from the str() method of x; the str() function prints the external structure of x (the fact that it is an object, and the number and type of its elements), whereas the str() method prints the internal structure of x (the external structure of all the properties contained by x).

By default (when error is F), the output is sent to the standard Eidos output stream.  When running at the command line, this sends it to stdout; when running in SLiMgui, this sends it to the simulation window’s output textview.  If error is T, the output is instead sent to the Eidos error stream.  When running at the command line, this sends it to stderr; when running in SLiMgui, the output is routed to the simulation’s debugging output window.

diff --git a/QtSLiM/help/SLiMHelpCallbacks.html b/QtSLiM/help/SLiMHelpCallbacks.html index e575a8f8..deabd68c 100644 --- a/QtSLiM/help/SLiMHelpCallbacks.html +++ b/QtSLiM/help/SLiMHelpCallbacks.html @@ -5,7 +5,7 @@ - + @@ -107,7 +105,7 @@

This function is written in Eidos, and its source code can be viewed with functionSource(), so you can copy and modify its code if you need to modify its functionality.

(object<MutationType>$)initializeMutationType(is$ id, numeric$ dominanceCoeff, string$ distributionType, ...)

Add a mutation type at initialization time.  The id must not already be used for any mutation type in the simulation.  The id parameter may be either an integer giving the ID of the new mutation type, or a string giving the name of the new mutation type (such as "m5" to specify an ID of 5).  The global symbol for the new mutation type, such as m5, is immediately available; the return value also provides the new object.

-

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be separately configured with the setDefaultDominanceForTrait() method if desired.

+

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but can subsequently be configured with the setDefaultHemizygousDominanceForTrait() method if desired.

The distributionType and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits.  The DES for the mutation type for a specific trait can subsequently be separately configured with the setEffectDistributionForTrait() method if desired.  The distributionType parameter may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for an exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  See the MutationType class documentation for discussion of the various DESs and their uses.

Note that by default in WF models, all mutations of a given mutation type will be converted into Substitution objects when they reach fixation, for efficiency reasons.  If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the convertToSubstitution property of MutationType to F.  In contrast, by default in nonWF models mutations will not be converted into Substitution objects when they reach fixation; convertToSubstitution is F by default in nonWF models.  To enable conversion in nonWF models for neutral mutation types with no indirect fitness effects, you should therefore set convertToSubstitution to T.

(object<MutationType>$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, string$ distributionType, ...)

@@ -127,7 +125,6 @@

Enable sex in the simulation.  Beginning in SLiM 5, this method should generally be passed NULL, simply indicating that sex should be enabled: individuals will then be male and female (rather than hermaphroditic), biparental crosses will be required to be between a female first parent and a male second parent, and selfing will not be allowed.  In this new configuration style, if a sexual simulation involving sex chromosomes is desired, the new initializeChromosome() call should be used to configure the chromosome setup for the simulation.

For backward compatibility, the old style of configuring a sexual simulation is still supported, however.  This implicitly defines a single chromosome, without a call to initializeChromosome().  With this old configuration approach, the chromosomeType parameter to initializeSex() gives the type of chromosome that should be simulated; this should be "A", "X", or "Y", and this chromosomeType value will be used as the symbol ("A", "X", or "Y") for the implicit chromosome.  These legacy chromosome types correspond to the new chromosome types "A", "X", and "-Y" respectively (note that it is not "Y"), when using initializeChromosome().  The implicit chromosome’s id property is always 1.  This old style of chromosome configuration is much less flexible, however, allowing only these three chromosome types, and only allowing a single chromosome to be set up.  This backward compatibility mode may be removed for SLiM in the future, and should be considered deprecated; new models should call initializeChromosome() explicitly instead.

There is no way to disable sex once it has been enabled; if you don’t want to have sex, don’t call this function.  If you require more flexibility with mating types and reproductive strategies than SLiM’s built-in support for sex provides, do not call initializeSex(); instead, track the sex or mating type of individuals yourself in script (with the tag property of Individual, for example), and manage the consequences of that in your script yourself, in terms of which individuals can mate with which, and exactly how the offspring is produced.

-

The xDominanceCoeff parameter has been deprecated and removed.  In SLiM 5 and later, use the hemizygousDominanceCoeff property of MutationType instead.  If the chromosomeType is "X", the optional xDominanceCoeff parameter can supply the dominance coefficient used when a mutation is present in an XY male, and is thus “heterozygous” (but in a different sense than the heterozygosity of an XX female with one copy of the mutation).

(void)initializeSLiMModelType(string$ modelType)

Configure the type of SLiM model used for the simulation.  At present, one of two model types may be selected.  If modelType is "WF", SLiM will use a Wright-Fisher (WF) model; this is the model type that has always been supported by SLiM, and is the model type used if initializeSLiMModelType() is not called.  If modelType is "nonWF", SLiM will use a non-Wright-Fisher (nonWF) model instead; this is a new model type supported by SLiM 3.0 and above.

If initializeSLiMModelType() is called at all then it must be called before any other initialization function, so that SLiM knows from the outset which features are enabled and which are not.

@@ -145,7 +142,7 @@

(void)initializeSpecies([integer$ tickModulo = 1], [integer$ tickPhase = 1], [string$ avatar = ""], [string$ color = ""])

Configure options for the species being initialized.  This initialization function may only be called in multispecies models (i.e., models with explicit species declarations); in single-species models, the default values are assumed and cannot be changed.

The tickModulo and tickPhase parameters determine the activation schedule for the species.  The active property of the species will be set to T (thus activating the species) every tickModulo ticks, beginning in tick tickPhase.  (However, when the species is activated in a given tick, the skipTick() method may still be called in a first() event to deactivate it.)  See the active property of Species for more details.

-

The avatar parameter, if not "", sets a string value used to represent the species graphically, particularly in SLiMgui but perhaps in other contexts also.  The avatar should generally be a single character – usually an emoji corresponding to the species, such as "🦊" for foxes or "🐭" for mice.  If avatar is the empty string, "", SLiMgui will choose a default avatar.

+

The avatar parameter, if not "", sets a string value used to represent the species graphically, particularly in SLiMgui but perhaps in other contexts also.  The avatar should generally be a single character – usually an emoji corresponding to the species, such as "🦊" for foxes or "🐭" for mice.  If avatar is the empty string, "", SLiMgui will choose a default avatar.

The color parameter, if not "", sets a string color value used to represent the species in SLiMgui.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB" (see the Eidos manual for details).  If color is the empty string, "", SLiMgui will choose a default color.

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [logical$ directFitnessEffect = F])

Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

@@ -166,7 +163,7 @@

3.2.  Nucleotide utilities

(is)codonsToAminoAcids(integer codons, [li$ long = F], [logical$ paste = T])

Returns the amino acid sequence corresponding to the codon sequence in codons.  Codons should be represented with values in [0, 63] where AAA is 0, AAC is 1, AAG is 2, and TTT is 63; see ancestralNucleotides() for discussion of this encoding.  If long is F (the default), the standard single-letter codes for amino acids will be used (where Serine is "S", etc.); if long is T, the standard three-letter codes will be used instead (where Serine is "Ser", etc.).  Beginning in SLiM 3.5, if long is 0, integer codes will be used as follows (and paste will be ignored):

-

stop (TAA, TAG, TGA) 0
+

stop (TAA, TAG, TGA) 0
Alanine 1
Arginine 2
Asparagine 3
@@ -209,7 +206,7 @@

The nucleotide sequence in sequence may be supplied in any of three formats: a string vector with single-letter nucleotides (e.g., "T", "A", "T", "A"), a singleton string of nucleotide letters (e.g., "TATA"), or an integer vector of nucleotide values (e.g., 3, 0, 3, 0) using SLiM’s standard code of A=0, C=1, G=2, T=3.  If the choice of format is not driven by other considerations, such as ease of manipulation, then the singleton string format will certainly be the most memory-efficient for long sequences, and will probably also be the fastest.  The nucleotide sequence provided must be a multiple of three in length, so that it translates to an integral number of codons.

(is)randomNucleotides(integer$ length, [Nif basis = NULL], [string$ format = "string"])

Generates a new random nucleotide sequence with length bases.  The four nucleotides ACGT are equally probable if basis is NULL (the default); otherwise, basis may be a 4-element integer or float vector providing relative fractions for A, C, G, and T respectively (these need not sum to 1.0, as they will be normalized).  More complex generative models such as Markov processes are not supported intrinsically in SLiM at this time, but arbitrary generated sequences may always be loaded from files on disk.

-

The format parameter controls the format of the returned sequence.  It may be "string" to obtain the generated sequence as a singleton string (e.g., "TATA"), "char" to obtain it as a string vector of single characters (e.g., "T", "A", "T", "A"), or "integer" to obtain it as an integer vector (e.g., 3, 0, 3, 0), using SLiM’s standard code of A=0, C=1, G=2, T=3.  For passing directly to initializeAncestralNucleotides(), format "string" (a singleton string) will certainly be the most memory-efficient, and probably also the fastest.  Memory efficiency can be a significant consideration; the nucleotide sequence for a chromosome of length 109 will occupy approximately 1 GB of memory when stored as a singleton string (with one byte per nucleotide), and much more if stored in the other formats.  However, the other formats can be easier to work with in Eidos, and so may be preferable for relatively short chromosomes if you are manipulating the generated sequence.

+

The format parameter controls the format of the returned sequence.  It may be "string" to obtain the generated sequence as a singleton string (e.g., "TATA"), "char" to obtain it as a string vector of single characters (e.g., "T", "A", "T", "A"), or "integer" to obtain it as an integer vector (e.g., 3, 0, 3, 0), using SLiM’s standard code of A=0, C=1, G=2, T=3.  For passing directly to initializeAncestralNucleotides(), format "string" (a singleton string) will certainly be the most memory-efficient, and probably also the fastest.  Memory efficiency can be a significant consideration; the nucleotide sequence for a chromosome of length 109 will occupy approximately 1 GB of memory when stored as a singleton string (with one byte per nucleotide), and much more if stored in the other formats.  However, the other formats can be easier to work with in Eidos, and so may be preferable for relatively short chromosomes if you are manipulating the generated sequence.

3.3.  Population genetics utilities

(float$)calcDxy(object<Haplosome> haplosomes1, object<Haplosome> haplosomes2, [No<Mutation> muts = NULL], [Ni$ start = NULL], [Ni$ end = NULL], [logical$ normalize = F])

Calculates the estimated Dxy between two Haplosome vectors for the set of mutations given in muts.  Dxy is the expected number of differences between two sequences, typically drawn from two different subpopulations whose haplosomes are given in haplosomes1 and haplosomes2.  It is therefore a metric of genetic divergence, comparable in some respects to FST; see Cruickshank and Hahn (2014, Molecular Ecology) for a discussion of FST versus Dxy.  This method implements Dxy as defined by Nei (1987) in Molecular Evolutionary Genomics (eq. 10.20), with optimizations for computational efficiency based upon an assumption that that multiallelic loci are rare (this is compatible with the infinite-sites model).

@@ -235,7 +232,7 @@

(float$)calcInbreedingLoad(object<Haplosome> haplosomes, [Nio<MutationType>$ mutType = NULL])

Calculates inbreeding load (the haploid number of lethal equivalents, or B) for a vector of haplosomes (containing at least one element) passed in haplosomes.  The calculation can be limited to a focal mutation type passed in mutType (which may be either an integer representing the ID of the desired mutation type, or a MutationType object specified directly); if mutType is NULL (the default), all of the mutations for the focal species will be considered.  In any case, only deleterious mutations (those with a negative selection coefficient) will be included in the final calculation.

The inbreeding load is a measure of the quantity of recessive deleterious variation that is heterozygous in a population and can contribute to fitness declines under inbreeding.  This function implements the following equation from Morton et al. (1956), which assumes no epistasis and random mating:

-

B = sum(qs) − sum(q2s) − 2sum(q(1−q)sh)

+

B = sum(qs) − sum(q2s) − 2sum(q(1−q)sh)

where q is the frequency of a given deleterious allele, s is the absolute value of the selection coefficient, and h is its dominance coefficient.  Note that the implementation, viewable with functionSource(), sets a maximum |s| of 1.0 (i.e., a lethal allele); |s| can sometimes be greater than 1.0 when s is drawn from a distribution, but in practice an allele with s < -1.0 has the same lethal effect as when s = -1.0.  Also note that this implementation will not work when the model changes the dominance coefficients of mutations using mutationEffect() callbacks, since it relies on the dominanceCoeff property of MutationType. Finally, note that, to estimate the diploid number of lethal equivalents (2B), the result from this function can simply be multiplied by two.

This function was contributed by Chris Kyriazis; thanks, Chris!

(float)calcLD_D(object<Mutation>$ mut1, [No<Mutation> mut2 = NULL], [No<Haplosome> haplosomes = NULL])

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index c14213fd..02a649fe 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -6012,7 +6012,7 @@ Note that dominance coefficients in SLiM have a quirk: they are stored internall \f4\fs20 \'92s dominance coefficient to some number \f3\fs18 x \f4\fs20 , -\f3\fs18 mut.dominanceCoeff==x +\f3\fs18 mut.dominance==x \f4\fs20 may be \f3\fs18 F \f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.\ @@ -6045,6 +6045,33 @@ Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM \f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 hemizygousDominance => (float)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The hemizygous dominance coefficient(s) of the mutation, taken from the default hemizygous dominance coefficient(s) of its +\f3\fs18 MutationType +\f4\fs20 . In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 hemizygousDominanceForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\f3\fs18 Mutation +\f4\fs20 objects will have a dynamic property named +\f3\fs18 heightHemizygousDominance +\f4\fs20 to access the hemizygous dominance for that trait. The hemizygous dominance coefficient(s) of a mutation can be changed with the +\f3\fs18 setHemizygousDominanceForTrait() +\f4\fs20 method.\ +Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +\f3\fs18 mut +\f4\fs20 \'92s hemizygous dominance coefficient to some number +\f3\fs18 x +\f4\fs20 , +\f3\fs18 mut.hemizygousDominance==x +\f4\fs20 may be +\f3\fs18 F +\f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf0 id => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6211,6 +6238,25 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the mutation\'92s hemizygous dominance coefficient for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 ; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient +\f1\i h +\f4\i0\fs13\fsmilli6667 \sub hemi +\fs20 \nosupersub . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 +\'a0(void)setDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6273,6 +6319,37 @@ The parameter \f4\fs20 to set the effect for each of the specified traits in one mutation before moving to the next mutation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 +\'a0(void)setHemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Sets the mutation\'92s hemizygous dominance coefficient(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +The parameter +\f3\fs18 dominance +\f4\fs20 must follow one of four patterns. In the first pattern, +\f3\fs18 dominance +\f4\fs20 is +\f3\fs18 NULL +\f4\fs20 ; this sets the dominance for each of the specified traits to the default hemizygous dominance coefficient from the mutation type of the mutation in each target mutation. (Note that mutation hemizygous dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default hemizygous dominance values.) In the second pattern, +\f3\fs18 dominance +\f4\fs20 is a singleton value; this sets the given hemizygous dominance for each of the specified traits in each target mutation. In the third pattern, +\f3\fs18 dominance +\f4\fs20 is of length equal to the number of specified traits; this sets the hemizygous dominance for each of the specified traits to the corresponding dominance value in each target mutation. In the fourth pattern, +\f3\fs18 dominance +\f4\fs20 is of length equal to the number of specified traits times the number of target mutations; this uses +\f3\fs18 dominance +\f4\fs20 to provide a different hemizygous dominance coefficient for each trait in each mutation, using consecutive values from +\f3\fs18 dominance +\f4\fs20 to set the hemizygous dominance for each of the specified traits in one mutation before moving to the next mutation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \'96\'a0(void)setMutationType(io$\'a0mutType)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6380,26 +6457,7 @@ SLiM consults this flag at the end of each tick when deciding whether to substit \f4\fs20 objects.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \kerning1\expnd0\expndtw0 hemizygousDominanceCoeff <\'96> (float$)\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf2 The dominance coefficient used for mutations of this type when they occur opposite a null haplosome (as can occur in sex-chromosome models and models involving a mix of haploids and diploids). This defaults to -\f3\fs18 1.0 -\f4\fs20 , and is used only in models where null haplosomes are present; the -\f3\fs18 dominanceCoeff -\f4\fs20 property is the dominance coefficient used in most circumstances. Changing this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the -\f3\fs18 Species -\f4\fs20 method -\f3\fs18 recalculateFitness() -\f4\fs20 \'96 but see the documentation of that method for caveats.\ -As with the -\f3\fs18 dominanceCoeff -\f4\fs20 property, this is stored internally using a single-precision float; see the documentation for -\f3\fs18 dominanceCoeff -\f4\fs20 for discussion.\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - -\f3\fs18 \cf0 id => (integer$)\ +\f3\fs18 \cf0 \kerning1\expnd0\expndtw0 id => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf0 The identifier for this mutation type; for mutation type @@ -6516,7 +6574,7 @@ The species to which the target object belongs.\ \f1\fs22 methods\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 \'96\'a0(float$)defaultDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\i0\fs18 \cf2 \'96\'a0(float)defaultDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous. The traits can be specified as @@ -6526,7 +6584,7 @@ The species to which the target object belongs.\ \f4\fs20 objects; \f3\fs18 NULL \f4\fs20 represents all of the traits in the species. The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their -\f3\fs18 dominanceCoeff +\f3\fs18 dominance \f4\fs20 property, but that can be changed later with the \f3\fs18 Mutation \f4\fs20 method @@ -6542,6 +6600,32 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 type normally used by Eidos. This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(float)defaultHemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the default hemizygous dominance coefficient used for the specified trait or traits, for mutations of this type when hemizygous (i.e., when present in only one copy because they are facing a null haplosome, such as for an X chromosome in a male). The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The default hemizygous dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their +\f3\fs18 hemizygousDominance +\f4\fs20 property, but that can be changed later with the +\f3\fs18 Mutation +\f4\fs20 method +\f3\fs18 setHemizygousDominanceForTrait() +\f4\fs20 .\ +Note that dominance coefficients are not bounded. A dominance coefficient greater than +\f3\fs18 1.0 +\f4\fs20 may be used to achieve an overdominance effect. By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ +Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision +\f3\fs18 float +\f4\fs20 , not the double-precision +\f3\fs18 float +\f4\fs20 type normally used by Eidos. This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \'96\'a0(float)drawEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6578,7 +6662,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 for all other DES types.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(string$)effectDistributionTypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(string)effectDistributionTypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the type of distribution of effects for the specified trait or traits. The traits can be specified as @@ -6616,12 +6700,28 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 objects; \f3\fs18 NULL \f4\fs20 represents all of the traits in the species. The value of -\f3\fs18 defaultDominance +\f3\fs18 dominance \f4\fs20 must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of \f3\fs18 defaultDominance \f4\fs20 is used for each corresponding trait).\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(void)setDefaultHemizygousDominanceForTrait(Nio\'a0trait, float\'a0dominance)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Set the default hemizygous dominance coefficient for a specified trait or traits, for the target mutation type. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. The value of +\f3\fs18 dominance +\f4\fs20 must either be singleton (in which case it is set as the default hemizygous dominance for all specified traits), or must match the number of specified traits (in which case one element of +\f3\fs18 dominance +\f4\fs20 is used for each corresponding trait).\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \'96\'a0(void)setEffectDistributionForTrait(Nio\'a0trait, string$\'a0distributionType, ...)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -13916,6 +14016,20 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 to access the effect for that trait.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 hemizygousDominance => (float)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The hemizygous dominance coefficient(s) of the mutation, carried over from the original mutation object. In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 hemizygousDominanceForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\f3\fs18 Substitution +\f4\fs20 objects will have a dynamic property named +\f3\fs18 heightHemizygousDominance +\f4\fs20 to access the dominance for that trait.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf0 id => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -14060,6 +14174,25 @@ nucleotide <\'96> (string$)\ \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effects for a given target substitution will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait \f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the substitution\'92s hemizygous dominance coefficient for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 , carried over from the original mutation object. For both multiplicative traits and additive traits this is the hemizygous dominance coefficient +\f1\i h +\f4\i0\fs13\fsmilli6667 \sub hemi +\fs20 \nosupersub . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 \f0\b\fs22 \cf2 5.19 Class Trait\ diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index fcf24ac6..75808d7b 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -1,9 +1,9 @@ {\rtf1\ansi\ansicpg1252\cocoartf2761 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Optima-Bold;\f1\fnil\fcharset0 Menlo-Regular;\f2\fswiss\fcharset0 Optima-Regular; -\f3\fswiss\fcharset0 Optima-Italic;\f4\froman\fcharset0 TimesNewRomanPSMT;\f5\fnil\fcharset0 Menlo-Bold; -\f6\fnil\fcharset0 AppleColorEmoji;\f7\froman\fcharset0 TimesNewRomanPS-ItalicMT;} -{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red150\green150\blue150;} -{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\cssrgb\c65500\c65500\c65500;} +\f3\fswiss\fcharset0 Optima-Italic;\f4\froman\fcharset0 TimesNewRomanPSMT;\f5\fnil\fcharset0 AppleColorEmoji; +\f6\froman\fcharset0 TimesNewRomanPS-ItalicMT;} +{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} +{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;} \margl1440\margr1440\vieww9000\viewh8400\viewkind0 \deftab397 \pard\pardeftab397\ri720\sb360\sa60\partightenfactor0 @@ -893,8 +893,12 @@ The \f1\fs18 1.0 \f2\fs20 complete dominance, and values greater than \f1\fs18 1.0 -\f2\fs20 , overdominance. The default dominance coefficient for the mutation type for a specific trait can subsequently be separately configured with the +\f2\fs20 , overdominance. The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the \f1\fs18 setDefaultDominanceForTrait() +\f2\fs20 method if desired. Note that the mutation type\'92s default hemizygous dominance coefficient is not supplied to this function; it always defaults to +\f1\fs18 1.0 +\f2\fs20 , but can subsequently be configured with the +\f1\fs18 setDefaultHemizygousDominanceForTrait() \f2\fs20 method if desired.\ The \f1\fs18 distributionType @@ -1170,22 +1174,6 @@ There is no way to disable sex once it has been enabled; if you don\'92t want to \f2\fs20 property of \f1\fs18 Individual \f2\fs20 , for example), and manage the consequences of that in your script yourself, in terms of which individuals can mate with which, and exactly how the offspring is produced.\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 - -\f0\b \cf2 The -\f5\fs18 xDominanceCoeff -\f0\fs20 parameter has been deprecated and removed. -\f2\b0 In SLiM 5 and later, use the -\f1\fs18 hemizygousDominanceCoeff -\f2\fs20 property of -\f1\fs18 MutationType -\f2\fs20 instead. \cf3 If the -\f1\fs18 chromosomeType -\f2\fs20 is -\f1\fs18 "X" -\f2\fs20 , the optional -\f1\fs18 xDominanceCoeff -\f2\fs20 parameter can supply the dominance coefficient used when a mutation is present in an XY male, and is thus \'93heterozygous\'94 (but in a different sense than the heterozygosity of an XX female with one copy of the mutation).\cf2 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f1\fs18 \cf0 (void)initializeSLiMModelType(string$\'a0modelType) @@ -1461,11 +1449,11 @@ The \f1\fs18 avatar \f2\fs20 should generally be a single character \'96 usually an emoji corresponding to the species, such as \f1\fs18 " -\f6\fs14 \uc0\u55358 \u56714 +\f5\fs14 \uc0\u55358 \u56714 \f1\fs18 " \f2\fs20 for foxes or \f1\fs18 " -\f6\fs14 \uc0\u55357 \u56365 +\f5\fs14 \uc0\u55357 \u56365 \f1\fs18 " \f2\fs20 for mice. If \f1\fs18 avatar @@ -2797,11 +2785,11 @@ The implementation \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f2\fs20 \cf2 Calculates -\f7\i \uc0\u960 +\f6\i \uc0\u960 \f2\i0 (nucleotide diversity, a metric of genetic diversity) for a vector of haplosomes (containing at least two elements), based upon the mutations in the haplosomes. -\f7\i \uc0\u960 +\f6\i \uc0\u960 \f2\i0 is computed by calculating the mean number of pairwise differences at each site, summing across all sites, and dividing by the number of sites. Therefore, it is interpretable as the number of differences per site expected between two randomly chosen sequences. The mathematical formulation (as an estimator of the population parameter -\f7\i \uc0\u952 +\f6\i \uc0\u952 \f2\i0 ) is based on work in Nei and Li (1979), Nei and Tajima (1981), and Tajima (1983; equation A3). The exact formula used here is common in textbooks (e.g., equations 9.1\'969.5 in Li 1997, equation 3.3 in Hahn 2018, or equation 2.2 in Coop 2020).\ Often \f1\fs18 haplosomes @@ -2821,7 +2809,7 @@ The calculation can be narrowed to apply to only a window \'96 a subrange of the \f2\fs20 of \f1\fs18 NULL \f2\fs20 , provides the haplosome-wide value of -\f7\i \uc0\u960 +\f6\i \uc0\u960 \f2\i0 .\ The implementation of \f1\fs18 calcPi() @@ -2830,7 +2818,7 @@ The implementation of \f2\fs20 , treats every mutation as independent in the heterozygosity calculations. One could regard this choice as embodying an infinite-sites interpretation of the segregating mutations, as with \f1\fs18 calcHeterozygosity() \f2\fs20 . Indeed, finite-sites models of -\f7\i \uc0\u960 +\f6\i \uc0\u960 \f2\i0 have been derived (Tajima 1996) though are not used here. In most biologically realistic models, such genetic states will be quite rare, and so the impact of this assumption will be negligible; however, in some models this distinction may be important. See \f1\fs18 calcPairHeterozygosity() \f2\fs20 for further discussion. This function was written by Nick Bailey (currently affiliated with CNRS and the Laboratory of Biometry and Evolutionary Biology at University Lyon 1), with helpful input from Peter Ralph and Chase Nelson.\ @@ -3001,9 +2989,9 @@ The implementation of \f2\fs20 . Indeed, Tajima\'92s \f3\i D \f2\i0 can be modified with finite-sites models of -\f7\i \uc0\u960 +\f6\i \uc0\u960 \f2\i0 and -\f7\i \uc0\u952 +\f6\i \uc0\u952 \f2\i0 (Misawa and Tajima 1997) though these are not used here. In most biologically realistic models, such genetic states will be quite rare, and so the impact of this assumption will be negligible; however, in some models this distinction may be important. See \f1\fs18 calcPairHeterozygosity() \f2\fs20 for further discussion. This function was written by Nick Bailey (currently affiliated with CNRS and the Laboratory of Biometry and Evolutionary Biology at University Lyon 1), with helpful input from Peter Ralph.\ diff --git a/VERSIONS b/VERSIONS index a1173a6e..b7a662e4 100644 --- a/VERSIONS +++ b/VERSIONS @@ -63,14 +63,23 @@ multitrait branch: add effect size and dominance coefficient properties to Mutation and Substitution (but not hooked up to the simulation yet) add -effectForTrait([Nio traits = NULL]) and -dominanceForTrait([Nio traits = NULL]) methods to Mutation and Substitution add +setEffectForTrait([Nio traits = NULL], [Nif effect = NULL]) and +setDominanceForTrait([Nio traits = NULL], [Nif dominance = NULL]) methods to Mutation - add Effect and Dominance properties to Mutation, both read-write float$ - add Effect and Dominance properties to Substitution, both read-only float$ + add Effect and Dominance properties to Mutation, both read-write float$ + add Effect and Dominance properties to Substitution, both read-only float$ remove Mutation method setSelectionCoeff(), autofixing to setEffectForTrait(NULL, ) rename the selectionCoeff property to effect, for both Mutation and Substitution; it changes from float$ to float, and now returns all trait effects; and SLiMgui autofixes this change remove the old C++ selection_coeff_ and dominance_coeff_ ivars in Mutation and Substitution, and begin the transition over to the new MutationTraitInfo struct add Individual method -(float)phenotypeForTrait([Nio traits = NULL]) to get trait values add Individual method +(void)setPhenotypeForTrait([Nio trait = NULL], [Nif phenotype = NULL]) to set trait values the addNewMutation() method's "selectionCoeff" parameter is now renamed to "effect"; SLiMgui will autofix this as needed + turn the hemizygous dominance coefficient into a first-class citizen handled identically to the regular dominance coefficient + MutationType: shift the C++ hemizygous_dominance_coeff_ property to be default_hemizygous_dominance_coeff_, kept per-trait + MutationType: remove the hemizygousDominanceCoeff property + MutationType: add defaultHemizygousDominanceForTrait([Nio trait = NULL]) and setDefaultHemizygousDominanceForTrait(Nio trait, float dominance) methods + Mutation: add a C++ hemizygous_dominance_coeff_ property to Mutation, per-trait, with a value inherited from MutationType's default_hemizygous_dominance_coeff_ property + Mutation and Substitution: add hemizygousDominance property and -hemizygousDominanceForTrait([Nio traits = NULL]) method + Mutation: add +setHemizygousDominanceForTrait([Nio traits = NULL], [Nif dominance = NULL]) method + Mutation: add read-write HemizygousDominance property to Mutation + Substitution: add read-only HemizygousDominance property version 5.1 (Eidos version 4.1): diff --git a/core/haplosome.cpp b/core/haplosome.cpp index d2da3be8..6a33e51e 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2940,7 +2940,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT } - // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... + // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... and hemizygous dominance... new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ @@ -3442,7 +3442,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); - // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... + // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... and hemizygous dominance... Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ @@ -4002,7 +4002,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt if (info_domcoeffs.size() > 0) dominance_coeff = info_domcoeffs[alt_allele_index]; else - dominance_coeff = mutation_type_ptr->DefaultDominanceForTrait(0); // FIXME MULTITRAIT + dominance_coeff = mutation_type_ptr->DefaultDominanceForTrait(0); // FIXME MULTITRAIT; also think about hemizygous dominance // get the selection coefficient from S, or draw one from the mutation type slim_effect_t selection_coeff; diff --git a/core/individual.cpp b/core/individual.cpp index 00c20abe..5d5f54c0 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -1755,7 +1755,9 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: { - // Here we implement a special behavior: you can do individual.traitName to access a trait value directly. + // Here we implement a special behavior: you can do individual. to access a trait value directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = subpopulation_->species_; Trait *trait = species.TraitFromStringID(p_property_id); @@ -2653,7 +2655,9 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue // all others, including gID_none default: { - // Here we implement a special behavior: you can do individual.traitName to access a trait value directly. + // Here we implement a special behavior: you can do individual. to access a trait value directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = subpopulation_->species_; Trait *trait = species.TraitFromStringID(p_property_id); @@ -5185,7 +5189,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (info_domcoeffs.size() > 0) dominance_coeff = info_domcoeffs[alt_allele_index]; else - dominance_coeff = mutation_type_ptr->DefaultDominanceForTrait(0); // FIXME MULTITRAIT + dominance_coeff = mutation_type_ptr->DefaultDominanceForTrait(0); // FIXME MULTITRAIT; also think about hemizygous dominance // get the selection coefficient from S, or draw one from the mutation type slim_effect_t selection_coeff; diff --git a/core/mutation.cpp b/core/mutation.cpp index d77599f9..0e2872bc 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -60,7 +60,6 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ int trait_count = mutation_block->trait_count_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); - slim_effect_t hemizygous_dominance = mutation_type_ptr_->hemizygous_dominance_coeff_; // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. @@ -76,9 +75,11 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // FIXME MULTITRAIT: This constructor needs to change to have a whole vector of trait information passed in, for effect and dominance slim_effect_t effect = trait_index ? p_selection_coeff : 0.0; slim_effect_t dominance = trait_index ? p_dominance_coeff : 0.5; + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably traitInfoRec->effect_size_ = effect; traitInfoRec->dominance_coeff_ = dominance; + traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; if (effect != 0.0) { @@ -148,7 +149,6 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ int trait_count = mutation_block->trait_count_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); - slim_effect_t hemizygous_dominance = mutation_type_ptr_->hemizygous_dominance_coeff_; // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. @@ -167,6 +167,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ traitInfoRec->effect_size_ = 0.0; traitInfoRec->dominance_coeff_ = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); + traitInfoRec->hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); if (traitType == TraitType::kMultiplicative) { @@ -195,9 +196,11 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ slim_effect_t effect = mutation_type_ptr_->DrawEffectForTrait(trait_index); slim_effect_t dominance = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); traitInfoRec->effect_size_ = effect; traitInfoRec->dominance_coeff_ = dominance; + traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; if (effect != 0.0) { @@ -262,7 +265,6 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ int trait_count = mutation_block->trait_count_; MutationTraitInfo *mut_trait_info = mutation_block->trait_info_buffer_ + trait_count * mutation_index; - slim_effect_t hemizygous_dominance = mutation_type_ptr_->hemizygous_dominance_coeff_; // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. @@ -278,9 +280,11 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // FIXME MULTITRAIT: The per-trait info will soon supplant selection_coeff_ and dominance_coeff_; this initialization code needs to be fleshed out slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : 0.0; slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : 0.5; + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); traitInfoRec->effect_size_ = effect; traitInfoRec->dominance_coeff_ = dominance; + traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; if (effect != 0.0) { @@ -338,6 +342,7 @@ void Mutation::SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, s { slim_effect_t old_effect = traitInfoRec->effect_size_; slim_effect_t dominance = traitInfoRec->dominance_coeff_; + slim_effect_t hemizygous_dominance = traitInfoRec->hemizygous_dominance_coeff_; traitInfoRec->effect_size_ = p_new_effect; @@ -356,9 +361,6 @@ void Mutation::SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, s } // cache values used by the fitness calculation code for speed; see header - // FIXME MULTICHROM: the hemizygous dominance coeff for a given mutation type could/should be per-trait; - // we cache hemizygous_effect_ for each trait separately anyway, so there's no waste there... - slim_effect_t hemizygous_dominance = mutation_type_ptr_->hemizygous_dominance_coeff_; if (traitType == TraitType::kMultiplicative) { @@ -427,12 +429,13 @@ void Mutation::SetDominance(TraitType traitType, MutationTraitInfo *traitInfoRec } } -void Mutation::HemizygousDominanceChanged(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) +void Mutation::SetHemizygousDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) { - // The hemizygous dominance coefficient is kept by the mutation type, so this does not actually set it, - // unlike SetEffect() and SetDominance() above. This is called from MutationType::SetProperty() when - // the hemizygous dominance coefficient changes, to ask us to recache fitness values. As with the - // SetDominance() method above, this has no effect on is_neutral_ and similar. + traitInfoRec->hemizygous_dominance_coeff_ = p_new_dominance; + + // We only need to recache the hemizygous_effect_ values, since only they are affected by the change in + // dominance coefficient. Changing dominance has no effect on is_neutral_ or any of the other is-neutral + // flags. So this is very simple. if (traitType == TraitType::kMultiplicative) { @@ -569,6 +572,34 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(float_result); } } + case gID_hemizygousDominance: + { + // This is not accelerated, because it's a bit tricky; each mutation could belong to a different species, + // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + const std::vector &traits = species.Traits(); + size_t trait_count = traits.size(); + + if (trait_count == 1) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[0].hemizygous_dominance_coeff_)); + else if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } + } // variables case gID_nucleotide: // ACCELERATED @@ -615,7 +646,10 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: - // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. + // Here we implement a special behavior: you can do mutation.Effect, mutation.Dominance, + // and mutation.HemizygousDominance to access a trait's values directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); @@ -629,6 +663,14 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) if (trait) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].effect_size_)); } + else if ((property_string.length() > 19) && Eidos_string_hasSuffix(property_string, "HemizygousDominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 19); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].hemizygous_dominance_coeff_)); + } else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) { std::string trait_name = property_string.substr(0, property_string.length() - 9); @@ -859,7 +901,9 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & default: { - // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. + // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); @@ -879,6 +923,20 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & return; } } + else if ((property_string.length() > 19) && Eidos_string_hasSuffix(property_string, "HemizygousDominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 19); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); + slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + + SetHemizygousDominance(trait->Type(), traitInfoRec, new_dominance); + return; + } + } else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) { std::string trait_name = property_string.substr(0, property_string.length() - 9); @@ -942,10 +1000,11 @@ EidosValue_SP Mutation::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, c { switch (p_method_id) { - case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); - case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); - case gID_setMutationType: return ExecuteMethod_setMutationType(p_method_id, p_arguments, p_interpreter); - default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); + case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_hemizygousDominanceForTrait: return ExecuteMethod_hemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_setMutationType: return ExecuteMethod_setMutationType(p_method_id, p_arguments, p_interpreter); + default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } } @@ -1025,6 +1084,44 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me } } +// ********************* - (float)hemizygousDominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP Mutation::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "hemizygousDominanceForTrait"); + + // get the trait info for this mutation + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } +} + // ********************* - (void)setMutationType(io$ mutType) // EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -1043,25 +1140,15 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth // If we are non-neutral, make sure the mutation type knows it is now also non-neutral // FIXME MULTITRAIT: I think it might be useful for MutationType to keep a flag separately for each trait, whether *that* trait is all_pure_neutral_DES_ or not - int trait_count = species.TraitCount(); - MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + //int trait_count = species.TraitCount(); + //MutationBlock *mutation_block = species.SpeciesMutationBlock(); + //MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); if (!is_neutral_) mutation_type_ptr_->all_pure_neutral_DES_ = false; - // Cache values used by the fitness calculation code for speed; changing the mutation type no longer changes - // the dominance coefficient, but hemizygous_dominance_coeff_ still comes from the muttype, and so might - // have changed. Note that is_neutral_ and similar do not change as a result of this, since the mutation - // effect remains the same (neutral or non-neutral). - for (int trait_index = 0; trait_index < trait_count; ++trait_index) - { - MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - Trait *trait = species.Traits()[trait_index]; - TraitType traitType = trait->Type(); - - HemizygousDominanceChanged(traitType, traitInfoRec, mutation_type_ptr_->hemizygous_dominance_coeff_); - } + // Changing the mutation type no longer changes the dominance coefficient or the hemizygous dominance + // coefficient, so there are no longer any side effects on trait effects / fitness to be managed here. return gStaticEidosValueVOID; } @@ -1098,6 +1185,7 @@ const std::vector *Mutation_Class::Properties(void) properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_position)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effect, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominance, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_subpopID)->DeclareAcceleratedSet(Mutation::SetProperty_Accelerated_subpopID)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_tag)->DeclareAcceleratedSet(Mutation::SetProperty_Accelerated_tag)); @@ -1119,8 +1207,10 @@ const std::vector *Mutation_Class::Methods(void) const methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setMutationType, kEidosValueMaskVOID))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); @@ -1133,8 +1223,9 @@ EidosValue_SP Mutation_Class::ExecuteClassMethod(EidosGlobalStringID p_method_id { switch (p_method_id) { - case gID_setEffectForTrait: return ExecuteMethod_setEffectForTrait(p_method_id, p_target, p_arguments, p_interpreter); - case gID_setDominanceForTrait: return ExecuteMethod_setDominanceForTrait(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setEffectForTrait: return ExecuteMethod_setEffectForTrait(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setDominanceForTrait: + case gID_setHemizygousDominanceForTrait: return ExecuteMethod_setDominanceForTrait(p_method_id, p_target, p_arguments, p_interpreter); default: return super::ExecuteClassMethod(p_method_id, p_target, p_arguments, p_interpreter); } @@ -1327,10 +1418,13 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI } // ********************* + (void)setDominanceForTrait([Nio trait = NULL], [Nif dominance = NULL]) +// ********************* + (void)setHemizygousDominanceForTrait([Nio trait = NULL], [Nif dominance = NULL]) // EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { #pragma unused (p_method_id, p_interpreter) + const char *method_name = (p_method_id == gID_setDominanceForTrait) ? "setDominanceForTrait" : "setHemizygousDominanceForTrait"; + EidosValue *trait_value = p_arguments[0].get(); EidosValue *dominance_value = p_arguments[1].get(); @@ -1346,14 +1440,14 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri Species *species = Community::SpeciesForMutations(p_target); if (!species) - EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setDominanceForTrait): setDominanceForTrait() requires that all mutations belong to the same species." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_" << method_name << "): " << method_name << "() requires that all mutations belong to the same species." << EidosTerminate(); MutationBlock *mutation_block = species->SpeciesMutationBlock(); const std::vector &traits = species->Traits(); // get the trait indices, with bounds-checking std::vector trait_indices; - species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDominanceForTrait"); + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, method_name); int trait_count = (int)trait_indices.size(); // note there is intentionally no bounds check of dominance coefficients @@ -1368,9 +1462,12 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationType *muttype = mut->mutation_type_ptr_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - slim_effect_t dominance = muttype->DefaultDominanceForTrait(trait_index); + slim_effect_t dominance = ((p_method_id == gID_setDominanceForTrait) ? muttype->DefaultDominanceForTrait(trait_index) : muttype->DefaultHemizygousDominanceForTrait(trait_index)); - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } } @@ -1390,7 +1487,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } else @@ -1404,7 +1504,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } } @@ -1424,7 +1527,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } } @@ -1449,7 +1555,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t dominance = static_cast(*(dominances_int++)); - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } else @@ -1464,7 +1573,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t dominance = static_cast(*(dominances_int++)); - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } } @@ -1486,7 +1598,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t dominance = static_cast(*(dominances_float++)); - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } else @@ -1501,14 +1616,17 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t dominance = static_cast(*(dominances_float++)); - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); } } } } } else - EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setDominanceForTrait): setDominanceForTrait() requires that dominance be (a) NULL, requesting the default dominance coefficient from the mutation's mutation type for each trait, (b) singleton, providing one dominance value for all traits, (c) equal in length to the number of traits in the species, providing one dominance value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one dominance value per trait per mutation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_" << method_name << "): " << method_name << "() requires that dominance be (a) NULL, requesting the default" << ((p_method_id == gID_setDominanceForTrait) ? " " : " hemizygous ") << "dominance coefficient from the mutation's mutation type for each trait, (b) singleton, providing one dominance value for all traits, (c) equal in length to the number of traits in the species, providing one dominance value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one dominance value per trait per mutation." << EidosTerminate(); return gStaticEidosValueVOID; } diff --git a/core/mutation.h b/core/mutation.h index 694a848f..26ef2c2c 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -59,8 +59,9 @@ typedef int32_t MutationIndex; // with a number of records per mutation that is determined when it is constructed. typedef struct _MutationTraitInfo { - slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) - slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) + slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t hemizygous_dominance_coeff_; // hemizygous dominance coefficient (h_hemi), inherited from MutationType by default // We cache values used in the fitness calculation code, for speed. These are the final fitness effects of this mutation // when it is homozygous or heterozygous, respectively. These values are clamped to a minimum of 0.0, so that multiplying @@ -68,7 +69,7 @@ typedef struct _MutationTraitInfo // values use slim_effect_t for speed; roundoff should not be a concern, since such differences would be inconsequential. slim_effect_t homozygous_effect_; // a cached value for 1 + s, clamped to 0.0 minimum; OR for 2a slim_effect_t heterozygous_effect_; // a cached value for 1 + hs, clamped to 0.0 minimum; OR for 2ha - slim_effect_t hemizygous_effect_; // a cached value for 1 + hs, clamped to 0.0 minimum; OR for 2ha (h = hemizygous_dominance_coeff_) + slim_effect_t hemizygous_effect_; // a cached value for 1 + hs, clamped to 0.0 minimum; OR for 2ha (h = h_hemi) } MutationTraitInfo; typedef enum { @@ -133,7 +134,7 @@ class Mutation : public EidosDictionaryRetained // These should be called whenever a mutation effect/dominance is changed; they handle the necessary recaching void SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect); void SetDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); - void HemizygousDominanceChanged(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); + void SetHemizygousDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline #if DEBUG_MUTATIONS @@ -158,8 +159,7 @@ class Mutation : public EidosDictionaryRetained virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; EidosValue_SP ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; - EidosValue_SP ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + EidosValue_SP ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism @@ -211,6 +211,7 @@ class Mutation_Class : public EidosDictionaryRetained_Class virtual EidosValue_SP ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const override; EidosValue_SP ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + EidosValue_SP ExecuteMethod_setHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; }; #endif /* defined(__SLiM__mutation__) */ diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 2e4f1757..244e3167 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -62,7 +62,7 @@ MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_i MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings) : #endif self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStringWithPrefix('m', p_mutation_type_id)), EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(this, gSLiM_MutationType_Class))), - species_(p_species), mutation_type_id_(p_mutation_type_id), hemizygous_dominance_coeff_(1.0), nucleotide_based_(p_nuc_based), convert_to_substitution_(false), stack_policy_(MutationStackPolicy::kStack), stack_group_(p_mutation_type_id), cached_DES_script_(nullptr) + species_(p_species), mutation_type_id_(p_mutation_type_id), nucleotide_based_(p_nuc_based), convert_to_substitution_(false), stack_policy_(MutationStackPolicy::kStack), stack_group_(p_mutation_type_id), cached_DES_script_(nullptr) #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES , muttype_registry_call_count_(0), keeping_muttype_registry_(false) #endif @@ -91,6 +91,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr EffectDistributionInfo DES_info; DES_info.default_dominance_coeff_ = static_cast(p_dominance_coeff); + DES_info.default_hemizygous_dominance_coeff_ = 1.0; DES_info.DES_type_ = p_DES_type; DES_info.DES_parameters_ = p_DES_parameters; DES_info.DES_strings_ = p_DES_strings; @@ -466,8 +467,6 @@ EidosValue_SP MutationType::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(color_sub_)); case gID_convertToSubstitution: return (convert_to_substitution_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); - case gID_hemizygousDominanceCoeff: - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(hemizygous_dominance_coeff_)); case gID_mutationStackGroup: return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(stack_group_)); case gID_nucleotideBased: @@ -577,51 +576,6 @@ void MutationType::SetProperty(EidosGlobalStringID p_property_id, const EidosVal return; } - case gID_hemizygousDominanceCoeff: - { - double value = p_value.FloatAtIndex_NOCAST(0, nullptr); - - hemizygous_dominance_coeff_ = static_cast(value); // intentionally no bounds check - - // Changing the hemizygous dominance coefficient means that the cached hemizygous fitness effects of - // all mutations using this type become invalid. We recache correct values for those mutations here. - // This is heavyweight for a property, but it is much simpler than having a deferred recache scheme, - // and changing the hemizygous dominance coefficient is expected to be extremely infrequent. - { - Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; - int registry_size; - const MutationIndex *registry_iter = species_.population_.MutationRegistry(®istry_size); - const MutationIndex *registry_iter_end = registry_iter + registry_size; - - while (registry_iter != registry_iter_end) - { - MutationIndex mut_index = (*registry_iter++); - Mutation *mut = mut_block_ptr + mut_index; - - if (mut->mutation_type_ptr_ == this) - { - MutationTraitInfo *mut_trait_info = mutation_block_->TraitInfoForIndex(mut_index); - - // loop over the traits and validate the cached hemizygous effect for each one - const std::vector &traits = species_.Traits(); - size_t trait_count = traits.size(); - - for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) - { - Trait *trait = traits[trait_index]; - - mut->HemizygousDominanceChanged(trait->Type(), mut_trait_info + trait_index, hemizygous_dominance_coeff_); - } - } - } - } - - // We also let the community know that a mutation type changed, for GUI redisplay - species_.community_.mutation_types_changed_ = true; - - return; - } - case gID_mutationStackGroup: { int64_t new_group = p_value.IntAtIndex_NOCAST(0, nullptr); @@ -713,13 +667,15 @@ EidosValue_SP MutationType::ExecuteInstanceMethod(EidosGlobalStringID p_method_i { switch (p_method_id) { - case gID_defaultDominanceForTrait: return ExecuteMethod_defaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); - case gID_effectDistributionTypeForTrait: return ExecuteMethod_effectDistributionTypeForTrait(p_method_id, p_arguments, p_interpreter); - case gID_effectDistributionParamsForTrait: return ExecuteMethod_effectDistributionParamsForTrait(p_method_id, p_arguments, p_interpreter); - case gID_drawEffectForTrait: return ExecuteMethod_drawEffectForTrait(p_method_id, p_arguments, p_interpreter); - case gID_setDefaultDominanceForTrait: return ExecuteMethod_setDefaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); - case gID_setEffectDistributionForTrait: return ExecuteMethod_setEffectDistributionForTrait(p_method_id, p_arguments, p_interpreter); - default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); + case gID_defaultDominanceForTrait: return ExecuteMethod_defaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_defaultHemizygousDominanceForTrait: return ExecuteMethod_defaultHemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_effectDistributionTypeForTrait: return ExecuteMethod_effectDistributionTypeForTrait(p_method_id, p_arguments, p_interpreter); + case gID_effectDistributionParamsForTrait: return ExecuteMethod_effectDistributionParamsForTrait(p_method_id, p_arguments, p_interpreter); + case gID_drawEffectForTrait: return ExecuteMethod_drawEffectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_setDefaultDominanceForTrait: return ExecuteMethod_setDefaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_setDefaultHemizygousDominanceForTrait: return ExecuteMethod_setDefaultHemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_setEffectDistributionForTrait: return ExecuteMethod_setEffectDistributionForTrait(p_method_id, p_arguments, p_interpreter); + default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } } @@ -751,6 +707,34 @@ EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalSt } } +// ********************* - (float$)defaultHemizygousDominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP MutationType::ExecuteMethod_defaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultHemizygousDominanceForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(DefaultHemizygousDominanceForTrait(trait_index))); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + float_result->push_float_no_check(DefaultHemizygousDominanceForTrait(trait_index)); + + return EidosValue_SP(float_result); + } +} + // ********************* - (fs)effectDistributionParamsForTrait([Nio trait = NULL]) // EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -929,6 +913,54 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba return gStaticEidosValueVOID; } +// ********************* - (void)setDefaultHemizygousDominanceForTrait(Nio trait, float dominance) +// +EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *dominance_value = p_arguments[1].get(); + int dominance_count = dominance_value->Count(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDefaultHemizygousDominanceForTrait"); + + if (dominance_count == 1) + { + // get the dominance coefficient + double dominance = dominance_value->FloatAtIndex_NOCAST(0, nullptr); + + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + + DES_info.default_hemizygous_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check + } + } + else if (dominance_count == (int)trait_indices.size()) + { + for (int dominance_index = 0; dominance_index < dominance_count; dominance_index++) + { + int64_t trait_index = trait_indices[dominance_index]; + EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + double dominance = dominance_value->FloatAtIndex_NOCAST(dominance_index, nullptr); + + DES_info.default_hemizygous_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check + } + } + else + EIDOS_TERMINATION << "ERROR (ExecuteMethod_setDefaultHemizygousDominanceForTrait): setDefaultHemizygousDominanceForTrait() requires parameter dominance to be of length 1, or equal in length to the number of specified traits." << EidosTerminate(nullptr); + + // BCH 7/2/2025: Changing the default dominance coefficient no longer means that the cached fitness + // effects of all mutations using this type become invalid; it is now just the *default* coefficient, + // and changing it does not change the state of mutations that have already derived from it. We do + // still want to let the community know that a mutation type has changed, though. + species_.community_.mutation_types_changed_ = true; + + return gStaticEidosValueVOID; +} + // ********************* - (void)setEffectDistributionForTrait(Nio trait, string$ distributionType, ...) // EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -940,7 +972,7 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo // get the trait indices, with bounds-checking std::vector trait_indices; - species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDefaultDominanceForTrait"); + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setEffectDistributionForTrait"); // Parse the DES type and parameters, and do various sanity checks DESType DES_type; @@ -999,7 +1031,6 @@ const std::vector *MutationType_Class::Properties(vo properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(MutationType::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_convertToSubstitution, false, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedSet(MutationType::SetProperty_Accelerated_convertToSubstitution)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominanceCoeff, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationStackGroup, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationStackPolicy, false, kEidosValueMaskString | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideBased, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))); @@ -1024,11 +1055,13 @@ const std::vector *MutationType_Class::Methods(void) c methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultDominanceForTrait, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultHemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString | kEidosValueMaskSingleton))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectDistributionForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); diff --git a/core/mutation_type.h b/core/mutation_type.h index c29ecd7a..20ea758d 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -64,7 +64,8 @@ std::ostream& operator<<(std::ostream& p_out, DESType p_DES_type); // This struct holds information about a distribution of effects (including dominance) for one trait. // MutationEffect then keeps a vector of these structs, one for each trait. typedef struct _EffectDistributionInfo { - slim_effect_t default_dominance_coeff_; // the default dominance coefficient (h) inherited by mutations of this type + slim_effect_t default_dominance_coeff_; // the default dominance coefficient (h) inherited by mutations of this type + slim_effect_t default_hemizygous_dominance_coeff_; // the default dominance coefficient (h) used when one haplosome is null DESType DES_type_; // distribution of effect size (DES) type (f: fixed, g: gamma, e: exponential, n: normal, w: Weibull) std::vector DES_parameters_; // DES parameters, of type double (originally float or integer type) @@ -98,9 +99,7 @@ class MutationType : public EidosDictionaryUnretained slim_objectid_t mutation_type_id_; // the id by which this mutation type is indexed in the chromosome EidosValue_SP cached_value_muttype_id_; // a cached value for mutation_type_id_; reset() if that changes - std::vector effect_distributions_; // DEs for each trait in the species - - slim_effect_t hemizygous_dominance_coeff_; // dominance coefficient (h) used when one haplosome is null // FIXME MULTITRAIT move into EffectDistributionInfo + std::vector effect_distributions_; // DESs for each trait in the species bool nucleotide_based_; // if true, the mutation type is nucleotide-based (i.e. mutations keep associated nucleotides) @@ -189,6 +188,13 @@ class MutationType : public EidosDictionaryUnretained return DES_info.default_dominance_coeff_; } + slim_effect_t DefaultHemizygousDominanceForTrait(int64_t p_trait_index) const + { + const EffectDistributionInfo &DES_info = effect_distributions_[p_trait_index]; + + return DES_info.default_hemizygous_dominance_coeff_; + } + slim_effect_t DrawEffectForTrait(int64_t p_trait_index) const; // draw a selection coefficient from the DE for a trait bool IsPureNeutralDES(void) const { return all_pure_neutral_DES_; } @@ -206,10 +212,12 @@ class MutationType : public EidosDictionaryUnretained virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; EidosValue_SP ExecuteMethod_defaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_defaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_effectDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_setDefaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism diff --git a/core/population.cpp b/core/population.cpp index 322e7ffa..fe1eefa6 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -8167,6 +8167,7 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit slim_effect_t dominance_coeff = mut_trait_info->dominance_coeff_; // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved; too edge to be bothered... + // FIXME MULTITRAIT: This will now change, since the hemizygous dominance coefficient is becoming a first-class citizen slim_objectid_t subpop_index = mutation_ptr->subpop_index_; slim_tick_t origin_tick = mutation_ptr->origin_tick_; slim_refcount_t prevalence = polymorphism.prevalence_; diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index 1ce2a4df..c7d25c90 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -2013,20 +2013,25 @@ EidosTypeSpecifier SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(std: const std::string &trait_name = trait_name_token->token_string_; const std::string &traitEffect_name = trait_name + "Effect"; const std::string &traitDominance_name = trait_name + "Dominance"; + const std::string &traitHemizygousDominance_name = trait_name + "HemizygousDominance"; EidosPropertySignature_CSP species_trait_signature((new EidosPropertySignature(trait_name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP individual_trait_signature((new EidosPropertySignature(trait_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP mutation_traitEffect_signature((new EidosPropertySignature(traitEffect_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP mutation_traitDominance_signature((new EidosPropertySignature(traitDominance_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP mutation_traitHemizygousDominance_signature((new EidosPropertySignature(traitHemizygousDominance_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP substitution_traitEffect_signature((new EidosPropertySignature(traitEffect_name, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP substitution_traitDominance_signature((new EidosPropertySignature(traitDominance_name, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP substitution_traitHemizygousDominance_signature((new EidosPropertySignature(traitHemizygousDominance_name, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); gSLiM_Species_Class->AddSignatureForProperty_TYPE_INTERPRETER(species_trait_signature); gSLiM_Individual_Class->AddSignatureForProperty_TYPE_INTERPRETER(individual_trait_signature); gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitEffect_signature); gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitDominance_signature); + gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitHemizygousDominance_signature); gSLiM_Substitution_Class->AddSignatureForProperty_TYPE_INTERPRETER(substitution_traitEffect_signature); gSLiM_Substitution_Class->AddSignatureForProperty_TYPE_INTERPRETER(substitution_traitDominance_signature); + gSLiM_Substitution_Class->AddSignatureForProperty_TYPE_INTERPRETER(substitution_traitHemizygousDominance_signature); } } diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index f807e2b1..b65aaeaf 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1252,10 +1252,11 @@ const std::string &gStr_position = EidosRegisteredString("position", gID_positio const std::string &gStr_subpopID = EidosRegisteredString("subpopID", gID_subpopID); const std::string &gStr_convertToSubstitution = EidosRegisteredString("convertToSubstitution", gID_convertToSubstitution); const std::string &gStr_defaultDominanceForTrait = EidosRegisteredString("defaultDominanceForTrait", gID_defaultDominanceForTrait); +const std::string &gStr_defaultHemizygousDominanceForTrait = EidosRegisteredString("defaultHemizygousDominanceForTrait", gID_defaultHemizygousDominanceForTrait); const std::string &gStr_effectDistributionTypeForTrait = EidosRegisteredString("effectDistributionTypeForTrait", gID_effectDistributionTypeForTrait); const std::string &gStr_effectDistributionParamsForTrait = EidosRegisteredString("effectDistributionParamsForTrait", gID_effectDistributionParamsForTrait); const std::string &gStr_dominance = EidosRegisteredString("dominance", gID_dominance); -const std::string &gStr_hemizygousDominanceCoeff = EidosRegisteredString("hemizygousDominanceCoeff", gID_hemizygousDominanceCoeff); +const std::string &gStr_hemizygousDominance = EidosRegisteredString("hemizygousDominance", gID_hemizygousDominance); const std::string &gStr_mutationStackGroup = EidosRegisteredString("mutationStackGroup", gID_mutationStackGroup); const std::string &gStr_mutationStackPolicy = EidosRegisteredString("mutationStackPolicy", gID_mutationStackPolicy); //const std::string &gStr_start = EidosRegisteredString("start", gID_start); @@ -1380,11 +1381,14 @@ const std::string &gStr_setMutationFractions = EidosRegisteredString("setMutatio const std::string &gStr_setMutationMatrix = EidosRegisteredString("setMutationMatrix", gID_setMutationMatrix); const std::string &gStr_effectForTrait = EidosRegisteredString("effectForTrait", gID_effectForTrait); const std::string &gStr_dominanceForTrait = EidosRegisteredString("dominanceForTrait", gID_dominanceForTrait); +const std::string &gStr_hemizygousDominanceForTrait = EidosRegisteredString("hemizygousDominanceForTrait", gID_hemizygousDominanceForTrait); const std::string &gStr_setEffectForTrait = EidosRegisteredString("setEffectForTrait", gID_setEffectForTrait); const std::string &gStr_setDominanceForTrait = EidosRegisteredString("setDominanceForTrait", gID_setDominanceForTrait); +const std::string &gStr_setHemizygousDominanceForTrait = EidosRegisteredString("setHemizygousDominanceForTrait", gID_setHemizygousDominanceForTrait); const std::string &gStr_setMutationType = EidosRegisteredString("setMutationType", gID_setMutationType); const std::string &gStr_drawEffectForTrait = EidosRegisteredString("drawEffectForTrait", gID_drawEffectForTrait); const std::string &gStr_setDefaultDominanceForTrait = EidosRegisteredString("setDefaultDominanceForTrait", gID_setDefaultDominanceForTrait); +const std::string &gStr_setDefaultHemizygousDominanceForTrait = EidosRegisteredString("setDefaultHemizygousDominanceForTrait", gID_setDefaultHemizygousDominanceForTrait); const std::string &gStr_setEffectDistributionForTrait = EidosRegisteredString("setEffectDistributionForTrait", gID_setEffectDistributionForTrait); const std::string &gStr_addPatternForClone = EidosRegisteredString("addPatternForClone", gID_addPatternForClone); const std::string &gStr_addPatternForCross = EidosRegisteredString("addPatternForCross", gID_addPatternForCross); diff --git a/core/slim_globals.h b/core/slim_globals.h index e75a37e7..947c7f26 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -843,10 +843,11 @@ extern const std::string &gStr_position; extern const std::string &gStr_subpopID; extern const std::string &gStr_convertToSubstitution; extern const std::string &gStr_defaultDominanceForTrait; +extern const std::string &gStr_defaultHemizygousDominanceForTrait; extern const std::string &gStr_effectDistributionTypeForTrait; extern const std::string &gStr_effectDistributionParamsForTrait; extern const std::string &gStr_dominance; -extern const std::string &gStr_hemizygousDominanceCoeff; +extern const std::string &gStr_hemizygousDominance; extern const std::string &gStr_mutationStackGroup; extern const std::string &gStr_mutationStackPolicy; //extern const std::string &gStr_start; now gEidosStr_start @@ -970,11 +971,14 @@ extern const std::string &gStr_setMutationFractions; extern const std::string &gStr_setMutationMatrix; extern const std::string &gStr_effectForTrait; extern const std::string &gStr_dominanceForTrait; +extern const std::string &gStr_hemizygousDominanceForTrait; extern const std::string &gStr_setEffectForTrait; extern const std::string &gStr_setDominanceForTrait; +extern const std::string &gStr_setHemizygousDominanceForTrait; extern const std::string &gStr_setMutationType; extern const std::string &gStr_drawEffectForTrait; extern const std::string &gStr_setDefaultDominanceForTrait; +extern const std::string &gStr_setDefaultHemizygousDominanceForTrait; extern const std::string &gStr_setEffectDistributionForTrait; extern const std::string &gStr_addPatternForClone; extern const std::string &gStr_addPatternForCross; @@ -1323,10 +1327,11 @@ enum _SLiMGlobalStringID : int { gID_subpopID, gID_convertToSubstitution, gID_defaultDominanceForTrait, + gID_defaultHemizygousDominanceForTrait, gID_effectDistributionTypeForTrait, gID_effectDistributionParamsForTrait, gID_dominance, - gID_hemizygousDominanceCoeff, + gID_hemizygousDominance, gID_mutationStackGroup, gID_mutationStackPolicy, //gID_start, now gEidosID_start @@ -1450,11 +1455,14 @@ enum _SLiMGlobalStringID : int { gID_setMutationMatrix, gID_effectForTrait, gID_dominanceForTrait, + gID_hemizygousDominanceForTrait, gID_setEffectForTrait, gID_setDominanceForTrait, + gID_setHemizygousDominanceForTrait, gID_setMutationType, gID_drawEffectForTrait, gID_setDefaultDominanceForTrait, + gID_setDefaultHemizygousDominanceForTrait, gID_setEffectDistributionForTrait, gID_addPatternForClone, gID_addPatternForCross, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index ace6a9db..8d07b732 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -42,6 +42,7 @@ void _RunMutationTypeTests(void) SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.effectDistributionParamsForTrait() == 0.0) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.effectDistributionTypeForTrait() == 'f') stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.defaultDominanceForTrait() == 0.5) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.defaultHemizygousDominanceForTrait() == 1.0) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.id == 1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.color = ''; } 2 early() { if (m1.color == '') stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.color = 'red'; } 2 early() { if (m1.color == 'red') stop(); }", __LINE__); @@ -70,6 +71,9 @@ void _RunMutationTypeTests(void) SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultDominanceForTrait(NULL, 0.3); }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultDominanceForTrait(0, 0.3); }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultDominanceForTrait(sim.traits, 0.3); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultHemizygousDominanceForTrait(NULL, 0.3); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultHemizygousDominanceForTrait(0, 0.3); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultHemizygousDominanceForTrait(sim.traits, 0.3); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (m1.effectDistributionTypeForTrait() == 'f' & m1.effectDistributionParamsForTrait() == 2.2) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'g' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3); if (m1.effectDistributionTypeForTrait() == 'e' & m1.effectDistributionParamsForTrait() == -3) stop(); }", __LINE__); @@ -1126,6 +1130,43 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(1,0), 1:10); if (!identical(mut.dominanceForTrait(c(1,0)), 1.0:10)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.dominanceForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + // MutationType defaultDominanceForTrait() and setDefaultDominanceForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultDominanceForTrait(0), 0.5)) stop(); } 5 late() { }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultDominanceForTrait(1), 0.5)) stop(); } 5 late() { }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultDominanceForTrait(c(0,1)), c(0.5, 0.5))) stop(); } 5 late() { }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(0, 0.25); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(NULL), c(0.25, 0.5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(1, 0.25); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(NULL), c(0.5, 0.25))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(NULL, c(0.25, 1.0)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(NULL), c(0.25, 1.0))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(c(0,1), c(0.25, 1.0)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(c(0,1)), c(0.25, 1.0))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(c(1,0), c(0.25, 1.0)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(c(1,0)), c(0.25, 1.0))) stop(); }"); + SLiMAssertScriptStop(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(c(1,0), c(0.25, 1.0)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(c(0,1)), c(0.25, 1.0))) stop(); }"); + + // Mutation hemizygousDominanceForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(0), 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(1), 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(1.0, 1.0))) stop(); }"); + + // Mutation setHemizygousDominanceForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(0, 3); mut.setHemizygousDominanceForTrait(1, 4.5); if (!identical(mut.hemizygousDominanceForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(0, 1:5 * 2 - 1); mut.setHemizygousDominanceForTrait(1, 1:5 * 2); if (!identical(mut.hemizygousDominanceForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(NULL, 1:10); if (!identical(mut.hemizygousDominanceForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(NULL, 1:10 + 0.5); if (!identical(mut.hemizygousDominanceForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(c(0,1), 1:10); if (!identical(mut.hemizygousDominanceForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(c(0,1), 1:10 + 0.5); if (!identical(mut.hemizygousDominanceForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(c(1,0), 1:10); if (!identical(mut.hemizygousDominanceForTrait(c(1,0)), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.hemizygousDominanceForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + + // MutationType defaultHemizygousDominanceForTrait() and setDefaultHemizygousDominanceForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultHemizygousDominanceForTrait(0), 1.0)) stop(); } 5 late() { }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultHemizygousDominanceForTrait(1), 1.0)) stop(); } 5 late() { }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultHemizygousDominanceForTrait(c(0,1)), c(1.0, 1.0))) stop(); } 5 late() { }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(0, 0.5); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(0.5, 1.0))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(1, 0.5); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(1.0, 0.5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(NULL, c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(0.25, 0.5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(c(0,1), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(0,1)), c(0.25, 0.5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(c(1,0), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(1,0)), c(0.25, 0.5))) stop(); }"); + SLiMAssertScriptStop(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(c(1,0), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(0,1)), c(0.25, 0.5))) stop(); }"); + // Substitution effectForTrait() SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(0), 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(1), 0.0)) stop(); }"); @@ -1136,26 +1177,40 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(1), 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(NULL), c(0.5, 0.5))) stop(); }"); - // Mutation Effect property + // Substitution hemizygousDominanceForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.hemizygousDominanceForTrait(0), 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.hemizygousDominanceForTrait(1), 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.hemizygousDominanceForTrait(NULL), c(1.0, 1.0))) stop(); }"); + + // Mutation Effect property SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightEffect, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightEffect, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffect = 0.25; if (!identical(mut.heightEffect, 0.25)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightEffect = 0.25; if (!identical(mut.weightEffect, 0.25)) stop(); }"); - // Mutation Dominance property + // Mutation Dominance property SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightDominance, 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightDominance, 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightDominance = 0.25; if (!identical(mut.heightDominance, 0.25)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightDominance = 0.25; if (!identical(mut.weightDominance, 0.25)) stop(); }"); - // Substitution Effect property + // Mutation HemizygousDominance property + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightHemizygousDominance, 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightHemizygousDominance, 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightHemizygousDominance = 0.25; if (!identical(mut.heightHemizygousDominance, 0.25)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightHemizygousDominance = 0.25; if (!identical(mut.weightHemizygousDominance, 0.25)) stop(); }"); + + // Substitution Effect property SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.heightEffect, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.weightEffect, 0.0)) stop(); }"); - // Substitution Dominance property + // Substitution Dominance property SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.heightDominance, 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.weightDominance, 0.5)) stop(); }"); + // Substitution HemizygousDominance property + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.heightHemizygousDominance, 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.weightHemizygousDominance, 1.0)) stop(); }"); } std::cout << "_RunMultitraitTests() done" << std::endl; diff --git a/core/species.cpp b/core/species.cpp index e0c6385e..859a598e 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1381,6 +1381,7 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos // BCH 7/2/2025: We no longer check the dominance coefficient against the mutation type, because it is allowed to differ // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved, or checked here; too edge to be bothered... + // FIXME MULTITRAIT: This will now change, since the hemizygous dominance coefficient is becoming a first-class citizen if ((nucleotide == -1) && mutation_type_ptr->nucleotide_based_) EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromTextFile): mutation type m"<< mutation_type_id << " is nucleotide-based, but a nucleotide value for a mutation of this type was not supplied." << EidosTerminate(); @@ -2139,6 +2140,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid // BCH 7/2/2025: We no longer check the dominance coefficient against the mutation type, because it is allowed to differ // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved, or checked here; too edge to be bothered... + // FIXME MULTITRAIT: This will now change, since the hemizygous dominance coefficient is becoming a first-class citizen if ((nucleotide == -1) && mutation_type_ptr->nucleotide_based_) EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): mutation type m" << mutation_type_id << " is nucleotide-based, but a nucleotide value for a mutation of this type was not supplied." << EidosTerminate(); @@ -2442,6 +2444,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid // BCH 7/2/2025: We no longer check the dominance coefficient against the mutation type, because it is allowed to differ // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved, or checked here; too edge to be bothered... + // FIXME MULTITRAIT: This will now change, since the hemizygous dominance coefficient is becoming a first-class citizen if ((nucleotide == -1) && mutation_type_ptr->nucleotide_based_) EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): mutation type m" << mutation_type_id << " is nucleotide-based, but a nucleotide value for a mutation of this type was not supplied." << EidosTerminate(); @@ -9841,7 +9844,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapconvert_to_substitution_)) { // this mutation is fixed, and the muttype wants substitutions, so make a substitution - // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec + // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec; likewise hemizygous dominance Substitution *sub = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->DefaultDominanceForTrait(0) /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); // FIXME MULTITRAIT population_.treeseq_substitutions_map_.emplace(position, sub); @@ -9855,7 +9858,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapNewMutationFromBlock(); - // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec + // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec; likewise hemizygous dominance Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->DefaultDominanceForTrait(0) /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); // FIXME MULTITRAIT // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index e2099497..62edacf9 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1629,6 +1629,9 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string if (trait->Name() == name) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name is unique within the species; there is already a trait in this species with the name '" << name << "'." << EidosTerminate(); + if (Eidos_string_hasSuffix(name, "Effect") || Eidos_string_hasSuffix(name, "Dominance") || Eidos_string_hasSuffix(name, "Hemizygous")) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name does not end in 'Effect', 'Dominance', or 'Hemizygous' to avoid naming conflicts and general confusion." << EidosTerminate(); + // type std::string type_string = type_value->StringAtIndex_NOCAST(0, nullptr); TraitType type; @@ -1698,9 +1701,10 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string EidosGlobalStringID trait_stringID = EidosStringRegistry::GlobalStringIDForString(name); EidosGlobalStringID traitEffect_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Effect"); EidosGlobalStringID traitDominance_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Dominance"); + EidosGlobalStringID traitHemizygousDominance_stringID = EidosStringRegistry::GlobalStringIDForString(name + "HemizygousDominance"); { - // add a Species property that returns the trait object + // add a Species property that returns the trait object const EidosPropertySignature *existing_signature = gSLiM_Species_Class->SignatureForProperty(trait_stringID); if (existing_signature) @@ -1725,7 +1729,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } { - // add an Individual property that returns the phenotype for the trait in an individual + // add an Individual property that returns the phenotype for the trait in an individual const EidosPropertySignature *existing_signature = gSLiM_Individual_Class->SignatureForProperty(trait_stringID); if (existing_signature) @@ -1748,7 +1752,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } { - // add a Mutation property that returns the effect size for the trait in a mutation + // add a Mutation Effect property that returns the effect size for the trait in a mutation const EidosPropertySignature *existing_signature = gSLiM_Mutation_Class->SignatureForProperty(traitEffect_stringID); if (existing_signature) @@ -1769,7 +1773,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } { - // add a Mutation property that returns the dominance for the trait in a mutation + // add a Mutation Dominance property that returns the dominance for the trait in a mutation const EidosPropertySignature *existing_signature = gSLiM_Mutation_Class->SignatureForProperty(traitDominance_stringID); if (existing_signature) @@ -1790,7 +1794,28 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } { - // add a Substitution property that returns the effect size for the trait in a substitution + // add a Mutation HemizygousDominance property that returns the hemizygous dominance for the trait in a mutation + const EidosPropertySignature *existing_signature = gSLiM_Mutation_Class->SignatureForProperty(traitHemizygousDominance_stringID); + + if (existing_signature) + { + if (!existing_signature->IsDynamicWithOwner("Trait") || + (existing_signature->value_mask_ != (kEidosValueMaskFloat | kEidosValueMaskSingleton)) || + (existing_signature->read_only_ == true)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Mutation class, but the name '" << name << "' conflicts with an existing property on Mutation. A different name must be used for this trait." << EidosTerminate(); + } + else + { + // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "HemizygousDominance", false, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + MarkAsDynamicWithOwner("Trait")); + + gSLiM_Mutation_Class->AddSignatureForProperty(signature); + } + } + + { + // add a Substitution Effect property that returns the effect size for the trait in a substitution const EidosPropertySignature *existing_signature = gSLiM_Substitution_Class->SignatureForProperty(traitEffect_stringID); if (existing_signature) @@ -1811,7 +1836,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } { - // add a Substitution property that returns the dominance for the trait in a substitution + // add a Substitution Dominance property that returns the dominance for the trait in a substitution const EidosPropertySignature *existing_signature = gSLiM_Substitution_Class->SignatureForProperty(traitDominance_stringID); if (existing_signature) @@ -1831,6 +1856,27 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } } + { + // add a Substitution HemizygousDominance property that returns the hemizygous dominance for the trait in a substitution + const EidosPropertySignature *existing_signature = gSLiM_Substitution_Class->SignatureForProperty(traitHemizygousDominance_stringID); + + if (existing_signature) + { + if (!existing_signature->IsDynamicWithOwner("Trait") || + (existing_signature->value_mask_ != (kEidosValueMaskFloat | kEidosValueMaskSingleton)) || + (existing_signature->read_only_ == false)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Substitution class, but the name '" << name << "' conflicts with an existing property on Substitution. A different name must be used for this trait." << EidosTerminate(); + } + else + { + // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "HemizygousDominance", true, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + MarkAsDynamicWithOwner("Trait")); + + gSLiM_Substitution_Class->AddSignatureForProperty(signature); + } + } + if (SLiM_verbosity_level >= 1) { std::ostream &output_stream = p_interpreter.ExecutionOutputStream(); @@ -2242,7 +2288,9 @@ EidosValue_SP Species::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: { - // Here we implement a special behavior: you can do species.traitName to access a trait object directly. + // Here we implement a special behavior: you can do species. to access a trait object directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Trait *trait = TraitFromStringID(p_property_id); if (trait) diff --git a/core/substitution.cpp b/core/substitution.cpp index 20dd0942..654be2ea 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -54,6 +54,7 @@ Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : { trait_info_[trait_index].effect_size_ = mut_trait_info[trait_index].effect_size_; trait_info_[trait_index].dominance_coeff_ = mut_trait_info[trait_index].dominance_coeff_; + trait_info_[trait_index].hemizygous_dominance_coeff_ = mut_trait_info[trait_index].hemizygous_dominance_coeff_; } } @@ -69,11 +70,13 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ trait_info_[0].effect_size_ = p_selection_coeff; trait_info_[0].dominance_coeff_ = p_dominance_coeff; + trait_info_[0].hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(0); // FIXME MULTITRAIT: needs to be passed in for (int trait_index = 1; trait_index < trait_count; trait_index++) { trait_info_[trait_index].effect_size_ = 0.0; trait_info_[trait_index].dominance_coeff_ = 0.0; + trait_info_[trait_index].hemizygous_dominance_coeff_ = 1.0; // FIXME MULTITRAIT: needs to be passed in } } @@ -98,7 +101,7 @@ void Substitution::PrintForSLiMOutput(std::ostream &p_out) const int trait_count = species.TraitCount(); for (int trait_index = 0; trait_index < trait_count; ++trait_index) - p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; + p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; // FIXME MULTITRAIT: hemizygous dominance coeff? // and then the remainder of the output line p_out << " p" << subpop_index_ << " " << origin_tick_ << " " << fixation_tick_; @@ -133,7 +136,7 @@ void Substitution::PrintForSLiMOutput_Tag(std::ostream &p_out) const int trait_count = species.TraitCount(); for (int trait_index = 0; trait_index < trait_count; ++trait_index) - p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; + p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; // FIXME MULTITRAIT: hemizygous dominance coeff? // and then the remainder of the output line p_out << " p" << subpop_index_ << " " << origin_tick_ << " " << fixation_tick_; @@ -242,6 +245,32 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(float_result); } } + case gID_hemizygousDominance: + { + // This is not accelerated, because it's a bit tricky; each substitution could belong to a different species, + // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + size_t trait_count = traits.size(); + + if (trait_count == 1) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[0].hemizygous_dominance_coeff_)); + else if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); + + for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } + } case gID_originTick: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(origin_tick_)); case gID_fixationTick: // ACCELERATED @@ -292,7 +321,10 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: - // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. + // Here we implement a special behavior: you can do substitution.Effect, substitution.Dominance, + // and substitution.HemizygousDominance to access a trait's values directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = mutation_type_ptr_->species_; const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); @@ -304,6 +336,14 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) if (trait) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].effect_size_)); } + else if ((property_string.length() > 19) && Eidos_string_hasSuffix(property_string, "HemizygousDominance")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 19); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].hemizygous_dominance_coeff_)); + } else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) { std::string trait_name = property_string.substr(0, property_string.length() - 9); @@ -528,9 +568,10 @@ EidosValue_SP Substitution::ExecuteInstanceMethod(EidosGlobalStringID p_method_i { switch (p_method_id) { - case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); - case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); - default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); + case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_hemizygousDominanceForTrait: return ExecuteMethod_hemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); + default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } } @@ -602,6 +643,40 @@ EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID } } +// ********************* - (float)hemizygousDominanceForTrait([Nio trait = NULL]) +// +EidosValue_SP Substitution::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "hemizygousDominanceForTrait"); + + if (trait_indices.size() == 1) + { + int64_t trait_index = trait_indices[0]; + slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; + + float_result->push_float_no_check(dominance); + } + + return EidosValue_SP(float_result); + } +} + // // Substitution_Class @@ -629,11 +704,12 @@ const std::vector *Substitution_Class::Properties(vo properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_position)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effect, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominance,true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_subpopID)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotide, false, kEidosValueMaskString | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotide)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotideValue)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_originTick)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_fixationTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_fixationTick)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_originTick)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_fixationTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_fixationTick)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_tag)); std::sort(properties->begin(), properties->end(), CompareEidosPropertySignatures); @@ -654,6 +730,7 @@ const std::vector *Substitution_Class::Methods(void) c methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/substitution.h b/core/substitution.h index 8306d442..8c57fbed 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -46,8 +46,9 @@ extern EidosClass *gSLiM_Substitution_Class; // rare and substitutions don't go away once created, so there is no need to overcomplicate this design. typedef struct _SubstitutionTraitInfo { - slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) - slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) + slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t hemizygous_dominance_coeff_; // hemizygous dominance coefficient (h_hemi), inherited from MutationType by default } SubstitutionTraitInfo; class Substitution : public EidosDictionaryRetained @@ -95,6 +96,7 @@ class Substitution : public EidosDictionaryRetained virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; EidosValue_SP ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); diff --git a/eidos/eidos_globals.h b/eidos/eidos_globals.h index 81ec0c6a..18a990e2 100644 --- a/eidos/eidos_globals.h +++ b/eidos/eidos_globals.h @@ -1327,7 +1327,7 @@ enum _EidosGlobalStringID : uint32_t gEidosID_Individual, gEidosID_LastEntry, // IDs added by the Context should start here - gEidosID_LastContextEntry = 560 // IDs added by the Context must end before this value; Eidos reserves the remaining values + gEidosID_LastContextEntry = 570 // IDs added by the Context must end before this value; Eidos reserves the remaining values }; extern std::vector gEidosConstantNames; // T, F, NULL, PI, E, INF, NAN From 7ab385670b7fb597b1cce3ec95cf3c2f8db57ca8 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Tue, 4 Nov 2025 10:01:21 -0500 Subject: [PATCH 029/107] make Substitution nucleotide[Value] properties read-only --- EidosScribe/EidosHelpController.mm | 2 ++ QtSLiM/help/SLiMHelpClasses.html | 4 ++-- SLiMgui/SLiMHelpClasses.rtf | 4 ++-- VERSIONS | 1 + core/substitution.cpp | 30 ++---------------------------- 5 files changed, 9 insertions(+), 32 deletions(-) diff --git a/EidosScribe/EidosHelpController.mm b/EidosScribe/EidosHelpController.mm index 9e84db33..4c1b204a 100644 --- a/EidosScribe/EidosHelpController.mm +++ b/EidosScribe/EidosHelpController.mm @@ -1289,6 +1289,8 @@ - (void)drawRow:(NSInteger)rowIndex clipRect:(NSRect)clipRect @"nucleotideBased =>", @"nucleotide <–>", @"nucleotideValue <–>", + @"nucleotide =>", + @"nucleotideValue =>", @"mutationMatrix =>", @"–\u00A0setMutationMatrix()", @"–\u00A0ancestralNucleotides()", diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index a62f16cd..446399fb 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -1353,9 +1353,9 @@

The tick in which this mutation fixed.

mutationType => (object<MutationType>$)

The MutationType from which this mutation was drawn.

-

nucleotide <–> (string$)

+

nucleotide => (string$)

A string representing the nucleotide associated with this mutation; this will be "A", "C", "G", or "T".  If the mutation is not nucleotide-based, this property is unavailable.

-

nucleotideValue <–> (integer$)

+

nucleotideValue => (integer$)

An integer representing the nucleotide associated with this mutation; this will be 0 (A), 1 (C), 2 (G), or 3 (T).  If the mutation is not nucleotide-based, this property is unavailable.

originTick => (integer$)

The tick in which this mutation arose.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 02a649fe..8f01c724 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -14056,7 +14056,7 @@ Note that this method is only for use in nonWF models, in which migration is man \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \expnd0\expndtw0\kerning0 -nucleotide <\'96> (string$)\ +nucleotide => (string$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 A @@ -14072,7 +14072,7 @@ nucleotide <\'96> (string$)\ \f4\fs20 . If the mutation is not nucleotide-based, this property is unavailable.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 nucleotideValue <\'96> (integer$)\ +\f3\fs18 \cf2 nucleotideValue => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 An diff --git a/VERSIONS b/VERSIONS index b7a662e4..e259768a 100644 --- a/VERSIONS +++ b/VERSIONS @@ -80,6 +80,7 @@ multitrait branch: Mutation: add +setHemizygousDominanceForTrait([Nio traits = NULL], [Nif dominance = NULL]) method Mutation: add read-write HemizygousDominance property to Mutation Substitution: add read-only HemizygousDominance property + policy change: the nucleotide and nucleotideValue properties of Substitution are now read-only (I think it was a bug that they were ever read-write...?) version 5.1 (Eidos version 4.1): diff --git a/core/substitution.cpp b/core/substitution.cpp index 654be2ea..dd4885be 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -516,32 +516,6 @@ void Substitution::SetProperty(EidosGlobalStringID p_property_id, const EidosVal // All of our strings are in the global registry, so we can require a successful lookup switch (p_property_id) { - case gID_nucleotide: - { - const std::string &nucleotide = ((EidosValue_String &)p_value).StringRefAtIndex_NOCAST(0, nullptr); - - if (nucleotide_ == -1) - EIDOS_TERMINATION << "ERROR (Substitution::SetProperty): property nucleotide is only defined for nucleotide-based substitutions." << EidosTerminate(); - - if (nucleotide == gStr_A) nucleotide_ = 0; - else if (nucleotide == gStr_C) nucleotide_ = 1; - else if (nucleotide == gStr_G) nucleotide_ = 2; - else if (nucleotide == gStr_T) nucleotide_ = 3; - else EIDOS_TERMINATION << "ERROR (Substitution::SetProperty): property nucleotide may only be set to 'A', 'C', 'G', or 'T'." << EidosTerminate(); - return; - } - case gID_nucleotideValue: - { - int64_t nucleotide = p_value.IntAtIndex_NOCAST(0, nullptr); - - if (nucleotide_ == -1) - EIDOS_TERMINATION << "ERROR (Substitution::SetProperty): property nucleotideValue is only defined for nucleotide-based substitutions." << EidosTerminate(); - if ((nucleotide < 0) || (nucleotide > 3)) - EIDOS_TERMINATION << "ERROR (Substitution::SetProperty): property nucleotideValue may only be set to 0 (A), 1 (C), 2 (G), or 3 (T)." << EidosTerminate(); - - nucleotide_ = (int8_t)nucleotide; - return; - } case gID_subpopID: { slim_objectid_t value = SLiMCastToObjectidTypeOrRaise(p_value.IntAtIndex_NOCAST(0, nullptr)); @@ -706,8 +680,8 @@ const std::vector *Substitution_Class::Properties(vo properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominance,true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_subpopID)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotide, false, kEidosValueMaskString | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotide)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotideValue)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotide, true, kEidosValueMaskString | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotide)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotideValue)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_originTick)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_fixationTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_fixationTick)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_tag)); From 9f3f894f572add45eaabc74a427b787cf42bf9fa Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 16 Nov 2025 12:52:12 -0500 Subject: [PATCH 030/107] fix new-ish crash with no-genetics models --- core/community.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/community.cpp b/core/community.cpp index c490ae6e..1abbdbcb 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -3418,11 +3418,14 @@ void Community::TabulateSLiMMemoryUsage_Community(SLiMMemoryUsage_Community *p_u for (Species *species : all_species_) { - MutationBlock *mutation_block = species->SpeciesMutationBlock(); - - p_usage->mutationRefcountBuffer += mutation_block->MemoryUsageForMutationRefcounts(); - p_usage->mutationPerTraitBuffer += mutation_block->MemoryUsageForTraitInfo(); - p_usage->mutationUnusedPoolSpace += mutation_block->MemoryUsageForFreeMutations(); + if (species->HasGenetics()) + { + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + + p_usage->mutationRefcountBuffer += mutation_block->MemoryUsageForMutationRefcounts(); + p_usage->mutationPerTraitBuffer += mutation_block->MemoryUsageForTraitInfo(); + p_usage->mutationUnusedPoolSpace += mutation_block->MemoryUsageForFreeMutations(); + } } // InteractionType From 2499c9e63cbc463def5d2b80e9c523bb4318db21 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 16 Nov 2025 14:12:24 -0500 Subject: [PATCH 031/107] fix #564, initializeMutationRateFromFile() needs a `sex` parameter --- QtSLiM/help/SLiMHelpFunctions.html | 4 ++-- SLiMgui/SLiMHelpFunctions.rtf | 6 ++++-- VERSIONS | 1 + core/slim_functions.cpp | 6 +++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index de883b66..25122212 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -99,9 +99,9 @@

If the optional sex parameter is "*" (the default), then the supplied mutation rate map will be used for both sexes (which is the only option for hermaphroditic simulations).  In sexual simulations sex may be "M" or "F" instead, in which case the supplied mutation rate map is used only for that sex (i.e., when generating a gamete from a parent of that sex).  In this case, two calls must be made to initializeMutationRate(), one for each sex, even if a rate of zero is desired for the other sex; no default mutation rate map is supplied.

In nucleotide-based models, initializeMutationRate() may not be called.  Instead, the desired sequence-based mutation rate(s) should be expressed in the mutationMatrix parameter to initializeGenomicElementType().  If variation in the mutation rate along the chromosome is desired, initializeHotspotMap() should be used.

The initializeMutationRateFromFile() function is a useful convenience function if you wish to read the mutation rate map from a file.

-

(void)initializeMutationRateFromFile(string$ path, integer$ lastPosition, [float$ scale = 1.0e-08], [string$ sep = "\t"], [string$ dec = "."])

+

(void)initializeMutationRateFromFile(string$ path, integer$ lastPosition, [float$ scale = 1.0e-08], [string$ sep = "\t"], [string$ dec = "."], [string$ sex = "*"])

Set a mutation rate map from data read from the file at path.  This function is essentially a wrapper for initializeMutationRate() that uses readCSV() and passes the data through.  The file is expected to contain two columns of data.  The first column must be integer start positions for rate map regions; the first region should start at position 0 if the map’s positions are 0-based, or at position 1 if the map’s positions are 1-based; in the latter case, 1 will be subtracted from every position since SLiM uses 0-based positions.  The second column must be float rates, relative to the scaling factor specified in scale; for example, if a given rate is 1.2 and scale is 1e-8 (the default), the rate used will be 1.2e-8.  No column header line should be present; the file should start immediately with numerical data.  The expected separator between columns is a tab character by default, but may be passed in sep; the expected decimal separator is a period by default, but may be passed in dec.  Once read, the map is converted into a rate map specified with end positions, rather than start positions, and the position given by lastPosition is used as the end of the last rate region; it should be the last position of the chromosome.

-

See readCSV() for further details on sep and dec, which are passed through to it; and see initializeMutationRate() for details on how the rate map is validated and used.

+

See readCSV() for further details on sep and dec, which are passed through to it; and see initializeMutationRate() for details on how the rate map is validated and used, and how the sex parameter is used.

This function is written in Eidos, and its source code can be viewed with functionSource(), so you can copy and modify its code if you need to modify its functionality.

(object<MutationType>$)initializeMutationType(is$ id, numeric$ dominanceCoeff, string$ distributionType, ...)

Add a mutation type at initialization time.  The id must not already be used for any mutation type in the simulation.  The id parameter may be either an integer giving the ID of the new mutation type, or a string giving the name of the new mutation type (such as "m5" to specify an ID of 5).  The global symbol for the new mutation type, such as m5, is immediately available; the return value also provides the new object.

diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 75808d7b..bf5cf3b9 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -812,7 +812,7 @@ If the optional \f2\fs20 function is a useful convenience function if you wish to read the mutation rate map from a file.\ \pard\pardeftab543\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf2 (void)initializeMutationRateFromFile(string$\'a0path, integer$\'a0lastPosition, [float$\'a0scale\'a0=\'a01.0e-08], [string$\'a0sep\'a0=\'a0"\\t"], [string$\'a0dec\'a0=\'a0"."])\ +\f1\fs18 \cf2 (void)initializeMutationRateFromFile(string$\'a0path, integer$\'a0lastPosition, [float$\'a0scale\'a0=\'a01.0e-08], [string$\'a0sep\'a0=\'a0"\\t"], [string$\'a0dec\'a0=\'a0"."], [string$\'a0sex\'a0=\'a0"*"])\ \pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 \f2\fs20 \cf2 Set a mutation rate map from data read from the file at @@ -862,7 +862,9 @@ See \f1\fs18 dec \f2\fs20 , which are passed through to it; and see \f1\fs18 initializeMutationRate() -\f2\fs20 for details on how the rate map is validated and used.\ +\f2\fs20 for details on how the rate map is validated and used, and how the +\f1\fs18 sex +\f2\fs20 parameter is used.\ This function is written in Eidos, and its source code can be viewed with \f1\fs18 functionSource() \f2\fs20 , so you can copy and modify its code if you need to modify its functionality.\ diff --git a/VERSIONS b/VERSIONS index a09384b8..a86f1444 100644 --- a/VERSIONS +++ b/VERSIONS @@ -83,6 +83,7 @@ multitrait branch: Mutation: add read-write HemizygousDominance property to Mutation Substitution: add read-only HemizygousDominance property policy change: the nucleotide and nucleotideValue properties of Substitution are now read-only (I think it was a bug that they were ever read-write...?) + fix #564, initializeMutationRateFromFile() needs a `sex` parameter; I'm doing this in multitrait to avoid the annoying doc conflicts version 5.1 (Eidos version 4.1): diff --git a/core/slim_functions.cpp b/core/slim_functions.cpp index 685085b5..d18f4b1d 100644 --- a/core/slim_functions.cpp +++ b/core/slim_functions.cpp @@ -93,7 +93,7 @@ const std::vector *Community::SLiMFunctionSignatures sim_func_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("summarizeIndividuals", SLiM_ExecuteFunction_summarizeIndividuals, kEidosValueMaskFloat, "SLiM"))->AddObject("individuals", gSLiM_Individual_Class)->AddInt("dim")->AddNumeric("spatialBounds")->AddString_S("operation")->AddLogicalEquiv_OSN("empty", gStaticEidosValue_Float0)->AddLogical_OS("perUnitArea", gStaticEidosValue_LogicalF)->AddString_OSN("spatiality", gStaticEidosValueNULL)); sim_func_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("treeSeqMetadata", SLiM_ExecuteFunction_treeSeqMetadata, kEidosValueMaskObject | kEidosValueMaskSingleton, gEidosDictionaryRetained_Class, "SLiM"))->AddString_S("filePath")->AddLogical_OS("userData", gStaticEidosValue_LogicalT)); - sim_func_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("initializeMutationRateFromFile", gSLiMSourceCode_initializeMutationRateFromFile, kEidosValueMaskVOID, "SLiM"))->AddString_S("path")->AddInt_S("lastPosition")->AddFloat_OS("scale", EidosValue_Float_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(1e-8)))->AddString_OS("sep", gStaticEidosValue_StringTab)->AddString_OS("dec", gStaticEidosValue_StringPeriod)); + sim_func_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("initializeMutationRateFromFile", gSLiMSourceCode_initializeMutationRateFromFile, kEidosValueMaskVOID, "SLiM"))->AddString_S("path")->AddInt_S("lastPosition")->AddFloat_OS("scale", EidosValue_Float_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(1e-8)))->AddString_OS("sep", gStaticEidosValue_StringTab)->AddString_OS("dec", gStaticEidosValue_StringPeriod)->AddString_OS("sex", gStaticEidosValue_StringAsterisk)); sim_func_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("initializeRecombinationRateFromFile", gSLiMSourceCode_initializeRecombinationRateFromFile, kEidosValueMaskVOID, "SLiM"))->AddString_S("path")->AddInt_S("lastPosition")->AddFloat_OS("scale", EidosValue_Float_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(1e-8)))->AddString_OS("sep", gStaticEidosValue_StringTab)->AddString_OS("dec", gStaticEidosValue_StringPeriod)->AddString_OS("sex", gStaticEidosValue_StringAsterisk)); // Internal SLiM functions @@ -1018,7 +1018,7 @@ R"V0G0N({ #pragma mark Other built-in functions #pragma mark - -#pragma mark (void)initializeMutationRateFromFile(s$ path, i$ lastPosition, [f$ scale=1e-8], [s$ sep="\t"], [s$ dec="."]) +#pragma mark (void)initializeMutationRateFromFile(s$ path, i$ lastPosition, [f$ scale=1e-8], [s$ sep="\t"], [s$ dec="."], [string$ sex = "*"]) const char *gSLiMSourceCode_initializeMutationRateFromFile = R"V0G0N({ errbase = "ERROR (initializeMutationRateFromFile): "; @@ -1055,7 +1055,7 @@ R"V0G0N({ else ends = c(ends[1:(size(ends)-1)] - base - 1, lastPosition); - initializeMutationRate(rates * scale, ends); + initializeMutationRate(rates * scale, ends, sex); })V0G0N"; #pragma mark (void)initializeRecombinationRateFromFile(s$ path, i$ lastPosition, [f$ scale=1e-8], [s$ sep="\t"], [s$ dec="."], [string$ sex = "*"]) From f6e4000d399abf047a6a0411d7b8abae68dd975b Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 16 Nov 2025 14:55:16 -0500 Subject: [PATCH 032/107] fix #570, make it easier to zero out migration rates --- QtSLiM/help/SLiMHelpClasses.html | 3 ++- SLiMgui/SLiMHelpClasses.rtf | 20 ++++++++++++++++---- VERSIONS | 4 ++++ core/slim_test_core.cpp | 5 ++++- core/subpopulation.cpp | 24 ++++++++++++++++++------ 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 446399fb..6ad56c37 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -1313,7 +1313,8 @@

– (void)setCloningRate(numeric rate)

Set the cloning rate of this subpopulation.  The rate is changed to rate, which should be between 0.0 and 1.0, inclusive (see the SLiM manual for further details).  Clonal reproduction can be enabled in both non-sexual (i.e. hermaphroditic) and sexual simulations.  In non-sexual simulations, rate must be a singleton value representing the overall clonal reproduction rate for the subpopulation.  In sexual simulations, rate may be either a singleton (specifying the clonal reproduction rate for both sexes) or a vector containing two numeric values (the female and male cloning rates specified separately, at indices 0 and 1 respectively).  During mating and offspring generation, the probability that any given offspring individual will be generated by cloning – by asexual reproduction without gametes or meiosis – will be equal to the cloning rate (for its sex, in sexual simulations) set in the parental (not the offspring!) subpopulation.

– (void)setMigrationRates(io<Subpopulation> sourceSubpops, numeric rates)

-

Set the migration rates to this subpopulation from the subpopulations in sourceSubpops to the corresponding rates specified in rates; in other words, rates gives the expected fractions of the children in this subpopulation that will subsequently be generated from parents in the subpopulations sourceSubpops (see the SLiM manual for further details).  This method will only set the migration fractions from the subpopulations given; migration rates from other subpopulations will be left unchanged (explicitly set a zero rate to turn off migration from a given subpopulation).  The type of sourceSubpops may be either integer, specifying subpopulations by identifier, or object, specifying subpopulations directly.

+

Set the migration rates to this subpopulation from the subpopulations in sourceSubpops to the corresponding rates specified in rates; in other words, rates gives the expected fractions of the children in this subpopulation that will subsequently be generated from parents in the subpopulations sourceSubpops (see section 24.2.1).  The rates parameter may be a singleton value, in which case that rate is used for all subpopulations in sourceSubpops.  This method will only set the migration fractions from the subpopulations given; migration rates from other subpopulations will be left unchanged (explicitly set a zero rate to turn off migration from a given subpopulation).  The type of sourceSubpops may be either integer, specifying subpopulations by identifier, or object, specifying subpopulations directly.

+

In general it is illegal to try to set the migration rate into a subpopulation from itself; that rate is, by definition, equal to the remainder after all migration from other subpopulations.  As a special case for convenience, it is legal to set a rate of 0.0 for all subpopulations in the species, including the target subpopulation.  For example, subpops.setMigrationRates(allSubpops, 0.0) will turn off all migration into the subpopulations in subpops.  The given rate of 0.0 from a subpop into itself is simply ignored, for this specific case only.

– (void)setSelfingRate(numeric$ rate)

Set the selfing rate of this subpopulation.  The rate is changed to rate, which should be between 0.0 and 1.0, inclusive (see the SLiM manual for further details).  Selfing can only be enabled in non-sexual (i.e. hermaphroditic) simulations.  During mating and offspring generation, the probability that any given offspring individual will be generated by selfing – by self-fertilization via gametes produced by meiosis by a single parent – will be equal to the selfing rate set in the parental (not the offspring!) subpopulation.

– (void)setSexRatio(float$ sexRatio)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 8f01c724..f3edab7a 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -13663,7 +13663,7 @@ This method is similar to getting the \f5 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 Set the migration rates to this subpopulation from the subpopulations in +\f4\fs20 \cf2 Set the migration rates to this subpopulation from the subpopulations in \f3\fs18 sourceSubpops \f4\fs20 to the corresponding rates specified in \f3\fs18 rates @@ -13671,14 +13671,26 @@ This method is similar to getting the \f3\fs18 rates \f4\fs20 gives the expected fractions of the children in this subpopulation that will subsequently be generated from parents in the subpopulations \f3\fs18 sourceSubpops -\f4\fs20 (see the SLiM manual for further details). This method will only set the migration fractions from the subpopulations given; migration rates from other subpopulations will be left unchanged (explicitly set a zero rate to turn off migration from a given subpopulation). The type of +\f4\fs20 (see section 24.2.1). The +\f3\fs18 rates +\f4\fs20 parameter may be a singleton value, in which case that rate is used for all subpopulations in +\f3\fs18 sourceSubpops +\f4\fs20 . This method will only set the migration fractions from the subpopulations given; migration rates from other subpopulations will be left unchanged (explicitly set a zero rate to turn off migration from a given subpopulation). The type of \f3\fs18 sourceSubpops \f4\fs20 may be either \f3\fs18 integer \f4\fs20 , specifying subpopulations by identifier, or \f3\fs18 object -\f4\fs20 , specifying subpopulations directly. -\f5 \ +\f4\fs20 , specifying subpopulations directly.\ +In general it is illegal to try to set the migration rate into a subpopulation from itself; that rate is, by definition, equal to the remainder after all migration from other subpopulations. As a special case for convenience, it is legal to set a rate of +\f3\fs18 0.0 +\f4\fs20 for all subpopulations in the species, including the target subpopulation. For example, +\f3\fs18 subpops.setMigrationRates(allSubpops, 0.0) +\f4\fs20 will turn off all migration into the subpopulations in +\f3\fs18 subpops +\f4\fs20 . The given rate of +\f3\fs18 0.0 +\f4\fs20 from a subpop into itself is simply ignored, for this specific case only.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 \'96\'a0(void)setSelfingRate(numeric$\'a0rate) diff --git a/VERSIONS b/VERSIONS index a86f1444..730bd261 100644 --- a/VERSIONS +++ b/VERSIONS @@ -84,6 +84,10 @@ multitrait branch: Substitution: add read-only HemizygousDominance property policy change: the nucleotide and nucleotideValue properties of Substitution are now read-only (I think it was a bug that they were ever read-write...?) fix #564, initializeMutationRateFromFile() needs a `sex` parameter; I'm doing this in multitrait to avoid the annoying doc conflicts + fix #570, setMigrationRates() should make it easier to stop all migration (done in multitrait to avoid the annoying doc conflicts) + specifically, this entails two changes: + allow a singleton migration rate value, applied for all subpops given + allow the destination subpop to be given as a source, iff all rates supplied are 0.0, to stop all migration: allSubpops.setMigrationRates(allSubpops, 0.0) version 5.1 (Eidos version 4.1): diff --git a/core/slim_test_core.cpp b/core/slim_test_core.cpp index 4df6303c..dedb9459 100644 --- a/core/slim_test_core.cpp +++ b/core/slim_test_core.cpp @@ -1337,10 +1337,13 @@ void _RunSubpopulationTests(void) SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(c(2, 4), c(0.1, 0.1)); } 10 early() { stop(); }", "not defined", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(c(2, 2), c(0.1, 0.1)); } 10 early() { stop(); }", "two rates set", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(c(p2, p2), c(0.1, 0.1)); } 10 early() { stop(); }", "two rates set", __LINE__); - SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(c(2, 3), 0.1); } 10 early() { stop(); }", "to be equal in size", __LINE__); + SLiMAssertScriptStop(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(c(2, 3), 0.1); } 10 early() { stop(); }", __LINE__); + SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(c(2, 3), float(0)); } 10 early() { stop(); }", "to be equal in size", __LINE__); + SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(c(2, 3), c(0.1, 0.1, 0.1)); } 10 early() { stop(); }", "to be equal in size", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(2, c(0.1, 0.1)); } 10 early() { stop(); }", "to be equal in size", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(2, -0.0001); } 10 early() { stop(); }", "within [0,1]", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(2, 1.0001); } 10 early() { stop(); }", "within [0,1]", __LINE__); + SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(c(2, 3), 0.6); } 10 early() { stop(); }", "must sum to <= 1.0", __LINE__, false); // raise is from EvolveSubpopulation(); we don't force constraints prematurely SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "1 early() { p1.setMigrationRates(c(2, 3), c(0.6, 0.6)); } 10 early() { stop(); }", "must sum to <= 1.0", __LINE__, false); // raise is from EvolveSubpopulation(); we don't force constraints prematurely // Test Subpopulation - (void)setSelfingRate(numeric$ rate) diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 9dac4f31..3f98829b 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -8975,22 +8975,34 @@ EidosValue_SP Subpopulation::ExecuteMethod_setMigrationRates(EidosGlobalStringID int source_subpops_count = sourceSubpops_value->Count(); int rates_count = rates_value->Count(); std::vector subpops_seen; + bool saw_nonzero_rate = false, saw_self_reference = false; - if (source_subpops_count != rates_count) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() requires sourceSubpops and rates to be equal in size." << EidosTerminate(); + if ((source_subpops_count != rates_count) && (rates_count != 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() requires sourceSubpops and rates to be equal in size, or rates to be singleton." << EidosTerminate(); for (int value_index = 0; value_index < source_subpops_count; ++value_index) { EidosObject *source_subpop = SLiM_ExtractSubpopulationFromEidosValue_io(sourceSubpops_value, value_index, &species_.community_, &species_, "setMigrationRates()"); // SPECIES CONSISTENCY CHECK slim_objectid_t source_subpop_id = ((Subpopulation *)(source_subpop))->subpopulation_id_; - + double migrant_fraction = ((rates_count == 1) ? rates_value->NumericAtIndex_NOCAST(0, nullptr) : rates_value->NumericAtIndex_NOCAST(value_index, nullptr)); + + // BCH 11/16/2025: We used to require that the target subpop was not a member of sourceSubpops; we would + // raise an error in all cases if that occurred. Now we relax those rules slightly, to make it easier + // to zero out all immigration into a subpop or subpops; we allow self-reference, but *only* if *all* + // rates specified in the call are 0.0. So you can do, e.g., allSubpops.setMigrationRates(allSubpops, 0). + // See https://github.com/MesserLab/SLiM/issues/570. As part of that fix, we also now allow rates to + // provide a singleton value, used for all sourceSubpops. + if (migrant_fraction != 0.0) + saw_nonzero_rate = true; if (source_subpop_id == subpopulation_id_) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() does not allow migration to be self-referential (originating within the destination subpopulation)." << EidosTerminate(); + saw_self_reference = true; + if (saw_self_reference && saw_nonzero_rate) + EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() does not allow migration to be self-referential (originating within the destination subpopulation), except when all rates are zero (for convenience)." << EidosTerminate(); + + // can't specify the same source subpopulation twice if (std::find(subpops_seen.begin(), subpops_seen.end(), source_subpop_id) != subpops_seen.end()) EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_setMigrationRates): setMigrationRates() two rates set for subpopulation p" << source_subpop_id << "." << EidosTerminate(); - double migrant_fraction = rates_value->NumericAtIndex_NOCAST(value_index, nullptr); - population_.SetMigration(*this, source_subpop_id, migrant_fraction); subpops_seen.emplace_back(source_subpop_id); } From ade0a888e12b5149824969f32c28255c7ceb6677 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 16 Nov 2025 21:42:57 -0500 Subject: [PATCH 033/107] fix #567, plot aspect ratio can differ from that requested --- QtSLiM/QtSLiMWindow.cpp | 34 ++++++++++++++++++++++++++++++++++ VERSIONS | 1 + 2 files changed, 35 insertions(+) diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index 09e2654f..6d5f3a3f 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -5034,6 +5034,40 @@ QtSLiMGraphView_CustomPlot *QtSLiMWindow::eidos_createPlot(QString title, double if (createdWindow) QtSLiMMakeWindowVisibleAndExposed(graphWindow); + + // BCH 11/16/2025: There is one tricky thing here, which is that in practice the plot window might not be allowed + // to be the requested size, due to screen constraints. We don't know that until we try. Before the call to + // the QtSLiMMakeWindowVisibleAndExposed() the window is still at the original size we requested (on macOS, at + // least). After that call, it has been constrained by whatever factors (screen size, dock/menubar, etc.) exist. + // We can't do anything about those constraints; but we do want to try to preserve the original aspect ratio + // requested by the user, and we emit a warning to the console. See https://github.com/MesserLab/SLiM/issues/567 + double realized_width = customPlot->width(), realized_height = customPlot->height(); + double trim_width = graphWindow->width() - realized_width, trim_height = graphWindow->height() - realized_height; + + if ((realized_width != width) || (realized_height != height)) + { + std::cout << "SLiMgui: the requested graph window size (" << width << ", " << height << ") was not attainable; the realized size was (" << + realized_width << ", " << realized_height << "). Resizing to try to preserve the requested aspect ratio." << std::endl; + + double requested_aspect_ratio = width / height; + double realized_aspect_ratio = realized_width / realized_height; + + if (realized_aspect_ratio > requested_aspect_ratio) + { + // the width is, proportionally, larger than requested and needs to be reduced + double corrected_width = std::round(requested_aspect_ratio * realized_height + trim_width); + graphWindow->resize(corrected_width, graphWindow->height()); + } + else if (realized_aspect_ratio < requested_aspect_ratio) + { + // the height is, proportionally, larger than requested and needs to be reduced + double corrected_height = std::round(realized_width / requested_aspect_ratio + trim_height); + graphWindow->resize(graphWindow->width(), corrected_height); + } + + //std::cout << " requested aspect ratio " << requested_aspect_ratio << "; final aspect ratio after correction " << + // (customPlot->width() / (double)customPlot->height()) << std::endl; + } } else { diff --git a/VERSIONS b/VERSIONS index 730bd261..7bf41e1c 100644 --- a/VERSIONS +++ b/VERSIONS @@ -88,6 +88,7 @@ multitrait branch: specifically, this entails two changes: allow a singleton migration rate value, applied for all subpops given allow the destination subpop to be given as a source, iff all rates supplied are 0.0, to stop all migration: allSubpops.setMigrationRates(allSubpops, 0.0) + fix #567, plot windows can have their aspect ratio distorted due to screen size and other constraints version 5.1 (Eidos version 4.1): From 756be7b37e9ba48014d5a1b559a8336294a37a40 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 5 Dec 2025 13:06:06 -0500 Subject: [PATCH 034/107] fix the doc merge for the previous commit --- EidosScribe/EidosHelpFunctions.rtf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EidosScribe/EidosHelpFunctions.rtf b/EidosScribe/EidosHelpFunctions.rtf index 965eb50c..8450ed80 100644 --- a/EidosScribe/EidosHelpFunctions.rtf +++ b/EidosScribe/EidosHelpFunctions.rtf @@ -6766,9 +6766,8 @@ Named \f1\fs18 c() \f3\fs20 function (including the possibility of type promotion).\ Since this function can be hard to understand at first, here is an example:\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\f1\fs18 \cf2 sapply(1:10, "if (applyValue % 2) applyValue ^ 2; else NULL;");\ +\f1\fs18 sapply(1:10, "if (applyValue % 2) applyValue ^ 2; else NULL;");\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f3\fs20 \cf2 \kerning1\expnd0\expndtw0 This produces the output From 004a7944a0f3db4616a096c75ff44ca5721cefac Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 13 Dec 2025 09:13:27 -0500 Subject: [PATCH 035/107] add eidos_simd.h to Xcode and QtCreator projects --- SLiM.xcodeproj/project.pbxproj | 2 ++ eidos/eidos.pro | 1 + 2 files changed, 3 insertions(+) diff --git a/SLiM.xcodeproj/project.pbxproj b/SLiM.xcodeproj/project.pbxproj index 3a6d3ea4..1b9b21b9 100644 --- a/SLiM.xcodeproj/project.pbxproj +++ b/SLiM.xcodeproj/project.pbxproj @@ -1953,6 +1953,7 @@ 98606AED1DED0DCD00821CFF /* mutation_run.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mutation_run.h; sourceTree = ""; }; 986070E82AACECD600FD6143 /* spatial_kernel.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = spatial_kernel.cpp; sourceTree = ""; }; 986070E92AACECD600FD6143 /* spatial_kernel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = spatial_kernel.h; sourceTree = ""; }; + 9860DCB22EEDAADD004B9CC0 /* eidos_simd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = eidos_simd.h; sourceTree = ""; }; 986764A92089066A00E81B2E /* tables.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = tables.h; path = treerec/tskit/tables.h; sourceTree = ""; }; 986926D21AA1337A0000E138 /* graph_submenu_H.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = graph_submenu_H.pdf; sourceTree = ""; }; 986926D31AA1337A0000E138 /* graph_submenu.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = graph_submenu.pdf; sourceTree = ""; }; @@ -2690,6 +2691,7 @@ isa = PBXGroup; children = ( 9873B12328CE26CD00582D83 /* eidos_openmp.h */, + 9860DCB22EEDAADD004B9CC0 /* eidos_simd.h */, 98E9A6B71A3CE35E000AD4FC /* eidos_rng.h */, 98E9A6B61A3CE35E000AD4FC /* eidos_rng.cpp */, 98321F931B406B67007337A3 /* eidos_globals.h */, diff --git a/eidos/eidos.pro b/eidos/eidos.pro index 91b51f07..6540cf89 100644 --- a/eidos/eidos.pro +++ b/eidos/eidos.pro @@ -130,6 +130,7 @@ HEADERS += \ eidos_property_signature.h \ eidos_rng.h \ eidos_script.h \ + eidos_simd.h \ eidos_sorting.h \ eidos_symbol_table.h \ eidos_test_builtins.h \ From 3460e796acd63e7cbfdc0aa3c8ea665c7cf8c3a5 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 18 Dec 2025 13:04:24 -0600 Subject: [PATCH 036/107] fix the merge of the project file from master --- SLiM.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/SLiM.xcodeproj/project.pbxproj b/SLiM.xcodeproj/project.pbxproj index d66e9433..1b9b21b9 100644 --- a/SLiM.xcodeproj/project.pbxproj +++ b/SLiM.xcodeproj/project.pbxproj @@ -2002,7 +2002,6 @@ 987AD8891B2CDBB80035D6C8 /* EidosConsoleWindowController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EidosConsoleWindowController.mm; sourceTree = ""; }; 987D19A3209A53850030D28D /* kastore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kastore.h; path = treerec/tskit/kastore/kastore.h; sourceTree = ""; }; 987D19A4209A53850030D28D /* kastore.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = kastore.c; path = treerec/tskit/kastore/kastore.c; sourceTree = ""; }; - 987D30EA2EF482AA0096621B /* eidos_simd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = eidos_simd.h; sourceTree = ""; }; 98800DC21B7EDCB50046F5F9 /* slim_test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = slim_test.cpp; sourceTree = ""; }; 98800DC31B7EDCB50046F5F9 /* slim_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = slim_test.h; sourceTree = ""; }; 98833AEA1C7A9E8D0072DBAC /* _README */ = {isa = PBXFileReference; lastKnownFileType = text; path = _README; sourceTree = ""; }; @@ -2693,7 +2692,6 @@ children = ( 9873B12328CE26CD00582D83 /* eidos_openmp.h */, 9860DCB22EEDAADD004B9CC0 /* eidos_simd.h */, - 987D30EA2EF482AA0096621B /* eidos_simd.h */, 98E9A6B71A3CE35E000AD4FC /* eidos_rng.h */, 98E9A6B61A3CE35E000AD4FC /* eidos_rng.cpp */, 98321F931B406B67007337A3 /* eidos_globals.h */, From 975ef7e64e5fa6a3f37bebde810e1f712d5bbbf8 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 20 Dec 2025 20:05:42 -0600 Subject: [PATCH 037/107] patch up merged doc --- EidosScribe/EidosHelpFunctions.rtf | 14 ++++++-------- QtSLiM/help/EidosHelpFunctions.html | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/EidosScribe/EidosHelpFunctions.rtf b/EidosScribe/EidosHelpFunctions.rtf index 47721cd8..7eb7e796 100644 --- a/EidosScribe/EidosHelpFunctions.rtf +++ b/EidosScribe/EidosHelpFunctions.rtf @@ -1968,10 +1968,9 @@ The \f1\fs18 \cf0 \kerning1\expnd0\expndtw0 (integer)rdunif(integer$\'a0n, [integer\'a0min\'a0=\'a00], [integer\'a0max \f2 \'a0 \f1 =\'a01])\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\f3\fs20 \cf2 \expnd0\expndtw0\kerning0 -Returns a vector of +\f3\fs20 \cf2 Returns a vector of \f1\fs18 n \f3\fs20 \f0\b random draws from a discrete uniform distribution @@ -1987,16 +1986,15 @@ Returns a vector of \f1\fs18 n \f3\fs20 , specifying a value for each draw. See \f1\fs18 runif() -\f3\fs20 for draws from a continuous uniform distribution\kerning1\expnd0\expndtw0 , and +\f3\fs20 for draws from a continuous uniform distribution, and \f1\fs18 runif64() \f3\fs20 for uniform draws from the full 64-bit \f1\fs18 integer -\f3\fs20 range\expnd0\expndtw0\kerning0 -.\ +\f3\fs20 range.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf2 \kerning1\expnd0\expndtw0 (integer)rdunif64(integer$\'a0n)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\f1\fs18 \cf2 (integer)rdunif64(integer$\'a0n)\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f3\fs20 \cf2 Returns a vector of \f1\fs18 n diff --git a/QtSLiM/help/EidosHelpFunctions.html b/QtSLiM/help/EidosHelpFunctions.html index f70a0718..7f9ab3e6 100644 --- a/QtSLiM/help/EidosHelpFunctions.html +++ b/QtSLiM/help/EidosHelpFunctions.html @@ -177,7 +177,7 @@

(float)rcauchy(integer$ n, [numeric location = 0], [numeric scale = 1])

Returns a vector of n random draws from a Cauchy distribution with location location and scale scale.  The location and scale parameters may either be singletons, specifying a single value to be used for all of the draws, or they may be vectors of length n, specifying a value for each draw.

(integer)rdunif(integer$ n, [integer min = 0], [integer max = 1])

-

Returns a vector of n random draws from a discrete uniform distribution from min to max, inclusive.  The min and max parameters may either be singletons, specifying a single value to be used for all of the draws, or they may be vectors of length n, specifying a value for each draw.  See runif() for draws from a continuous uniform distribution, and runif64() for uniform draws from the full 64-bit integer range.

+

Returns a vector of n random draws from a discrete uniform distribution from min to max, inclusive.  The min and max parameters may either be singletons, specifying a single value to be used for all of the draws, or they may be vectors of length n, specifying a value for each draw.  See runif() for draws from a continuous uniform distribution, and runif64() for uniform draws from the full 64-bit integer range.

(integer)rdunif64(integer$ n)

Returns a vector of n random draws from a discrete uniform distribution spanning the full 64-bit integer range.  See rdunif() for draws from a discrete uniform distribution with a specified range.

(float)rexp(integer$ n, [numeric mu = 1])

From 9519cbb51090901d71ff7800fdd3a52a6c2f2843 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Tue, 23 Dec 2025 11:23:25 -0600 Subject: [PATCH 038/107] patch up non-text files after rdirichlet() merge --- EidosScribe/EidosHelpFunctions.rtf | 2 +- SLiM.xcodeproj/project.pbxproj | 40 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/EidosScribe/EidosHelpFunctions.rtf b/EidosScribe/EidosHelpFunctions.rtf index d975a1d5..9bf1a608 100644 --- a/EidosScribe/EidosHelpFunctions.rtf +++ b/EidosScribe/EidosHelpFunctions.rtf @@ -1966,7 +1966,7 @@ The \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f1\fs18 \cf2 \kerning1\expnd0\expndtw0 (float)rdirichlet(integer$\'a0n, numeric\'a0alpha)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f3\fs20 \cf2 Returns a matrix of \f1\fs18 n diff --git a/SLiM.xcodeproj/project.pbxproj b/SLiM.xcodeproj/project.pbxproj index 023c1327..6ba60f8e 100644 --- a/SLiM.xcodeproj/project.pbxproj +++ b/SLiM.xcodeproj/project.pbxproj @@ -306,6 +306,15 @@ 985724F31AD6D4060047C223 /* show_tokens.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 985724EF1AD6D4060047C223 /* show_tokens.pdf */; }; 985724F61AD6DD470047C223 /* show_execution_H.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 985724F41AD6DD470047C223 /* show_execution_H.pdf */; }; 985724F71AD6DD470047C223 /* show_execution.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 985724F51AD6DD470047C223 /* show_execution.pdf */; }; + 985C533C2EFB071900E51591 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 985C533B2EFB071900E51591 /* dirichlet.c */; }; + 985C533D2EFB071900E51591 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 985C533B2EFB071900E51591 /* dirichlet.c */; }; + 985C533E2EFB071900E51591 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 985C533B2EFB071900E51591 /* dirichlet.c */; }; + 985C533F2EFB071900E51591 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 985C533B2EFB071900E51591 /* dirichlet.c */; }; + 985C53402EFB071900E51591 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 985C533B2EFB071900E51591 /* dirichlet.c */; }; + 985C53412EFB071900E51591 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 985C533B2EFB071900E51591 /* dirichlet.c */; }; + 985C53422EFB071900E51591 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 985C533B2EFB071900E51591 /* dirichlet.c */; }; + 985C53432EFB071900E51591 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 985C533B2EFB071900E51591 /* dirichlet.c */; }; + 985C53442EFB071900E51591 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 985C533B2EFB071900E51591 /* dirichlet.c */; }; 985D1D8B2808B84F00461CFA /* sparse_vector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 985D1D892808B84F00461CFA /* sparse_vector.cpp */; }; 985D1D8C2808B84F00461CFA /* sparse_vector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 985D1D892808B84F00461CFA /* sparse_vector.cpp */; }; 985F3EEA24BA27EC00E712E0 /* slim_test_genetics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 985F3EE924BA27EC00E712E0 /* slim_test_genetics.cpp */; }; @@ -652,15 +661,6 @@ 98B674222E981AD400930737 /* trait.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 98B6741D2E981AD400930737 /* trait.cpp */; }; 98C0943E1B7663DF00766A9A /* female_symbol.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98C0943C1B7663DF00766A9A /* female_symbol.pdf */; }; 98C0943F1B7663DF00766A9A /* male_symbol.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 98C0943D1B7663DF00766A9A /* male_symbol.pdf */; }; - 98C634442EF9F632003F12A3 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C634432EF9F632003F12A3 /* dirichlet.c */; }; - 98C634452EF9F632003F12A3 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C634432EF9F632003F12A3 /* dirichlet.c */; }; - 98C634462EF9F632003F12A3 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C634432EF9F632003F12A3 /* dirichlet.c */; }; - 98C634472EF9F632003F12A3 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C634432EF9F632003F12A3 /* dirichlet.c */; }; - 98C634482EF9F632003F12A3 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C634432EF9F632003F12A3 /* dirichlet.c */; }; - 98C634492EF9F632003F12A3 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C634432EF9F632003F12A3 /* dirichlet.c */; }; - 98C6344A2EF9F632003F12A3 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C634432EF9F632003F12A3 /* dirichlet.c */; }; - 98C6344B2EF9F632003F12A3 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C634432EF9F632003F12A3 /* dirichlet.c */; }; - 98C6344C2EF9F632003F12A3 /* dirichlet.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C634432EF9F632003F12A3 /* dirichlet.c */; }; 98C821241C7A980000548839 /* inline.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C820E51C7A980000548839 /* inline.c */; }; 98C821251C7A980000548839 /* math.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C820E61C7A980000548839 /* math.c */; }; 98C821261C7A980000548839 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 98C820E91C7A980000548839 /* error.c */; }; @@ -1946,6 +1946,7 @@ 985724F51AD6DD470047C223 /* show_execution.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = show_execution.pdf; sourceTree = ""; }; 9857E33A1BB58DAE00F1C8A9 /* eidos_intrusive_ptr.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = eidos_intrusive_ptr.h; sourceTree = ""; }; 985A11861BBA07CB009EE1FF /* eidos_object_pool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eidos_object_pool.h; sourceTree = ""; }; + 985C533B2EFB071900E51591 /* dirichlet.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dirichlet.c; sourceTree = ""; }; 985D1D892808B84F00461CFA /* sparse_vector.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sparse_vector.cpp; sourceTree = ""; }; 985D1D8A2808B84F00461CFA /* sparse_vector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sparse_vector.h; sourceTree = ""; }; 985DCFD52B1ED7340025B0D5 /* PARALLEL */ = {isa = PBXFileReference; lastKnownFileType = text; path = PARALLEL; sourceTree = ""; }; @@ -2069,7 +2070,6 @@ 98B8AF8725A2BE7200C95D66 /* json_fwd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json_fwd.hpp; sourceTree = ""; }; 98C0943C1B7663DF00766A9A /* female_symbol.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = female_symbol.pdf; sourceTree = ""; }; 98C0943D1B7663DF00766A9A /* male_symbol.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = male_symbol.pdf; sourceTree = ""; }; - 98C634432EF9F632003F12A3 /* dirichlet.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dirichlet.c; sourceTree = ""; }; 98C820E11C7A980000548839 /* build.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = build.h; sourceTree = ""; }; 98C820E31C7A980000548839 /* gsl_complex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gsl_complex.h; sourceTree = ""; }; 98C820E41C7A980000548839 /* gsl_complex_math.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gsl_complex_math.h; sourceTree = ""; }; @@ -2984,7 +2984,7 @@ 98C820F81C7A980000548839 /* binomial_tpe.c */, 988880EB20744EE800E10172 /* cauchy.c */, 98D7D6642AB24CBC002AFE34 /* chisq.c */, - 98C634432EF9F632003F12A3 /* dirichlet.c */, + 985C533B2EFB071900E51591 /* dirichlet.c */, 98C821A71C7A9B1600548839 /* discrete.c */, 98C820F91C7A980000548839 /* exponential.c */, 980566E125A7C5B9008D3C7F /* fdist.c */, @@ -3735,7 +3735,7 @@ 98D7D6612AB24C40002AFE34 /* tdist.c in Sources */, 9876E60B1ED55B5000FF9762 /* erfc.c in Sources */, 981DC35F28E26F8B000ABE91 /* eidos_functions_matrices.cpp in Sources */, - 98C634492EF9F632003F12A3 /* dirichlet.c in Sources */, + 985C53432EFB071900E51591 /* dirichlet.c in Sources */, 9876E6101ED55C0C00FF9762 /* expint.c in Sources */, 98C821981C7A983800548839 /* trig.c in Sources */, 982556BC1BA8EF990054CB3F /* eidos_test.cpp in Sources */, @@ -3855,7 +3855,6 @@ 98EFE62F1ADB611100CBEC78 /* eidos_symbol_table.cpp in Sources */, 9893C7E91CDFE9870029AC94 /* eidos_type_interpreter.cpp in Sources */, 986D73E81B07E89E007FBB70 /* eidos_call_signature.cpp in Sources */, - 98C6344A2EF9F632003F12A3 /* dirichlet.c in Sources */, 98CEFD102AAFABAA00D2C9B4 /* interp.c in Sources */, 98332ADC1FDBC0D000274FF0 /* dgemv.c in Sources */, 98332B111FDBD09800274FF0 /* init.c in Sources */, @@ -3944,6 +3943,7 @@ 98D7D6652AB24CBC002AFE34 /* chisq.c in Sources */, 985724D51AD481070047C223 /* eidos_test.cpp in Sources */, 98CEFD752AAFB12F00D2C9B4 /* cspline.c in Sources */, + 985C533E2EFB071900E51591 /* dirichlet.c in Sources */, 98EDB5112E65410C00CC8798 /* copy.c in Sources */, 986070EA2AACECD600FD6143 /* spatial_kernel.cpp in Sources */, 98332AB41FDBA1E100274FF0 /* blas.c in Sources */, @@ -4034,7 +4034,6 @@ 986152632B167B4E0083E68F /* beta.c in Sources */, 9861526C2B167B4E0083E68F /* multinomial.c in Sources */, 986152712B167B4E0083E68F /* dtrsv.c in Sources */, - 98C634462EF9F632003F12A3 /* dirichlet.c in Sources */, 9861525E2B167B4E0083E68F /* laplace.c in Sources */, 986151FD2B167A6A0083E68F /* eidos_rng.cpp in Sources */, 986152732B167B4E0083E68F /* binomial_tpe.c in Sources */, @@ -4123,6 +4122,7 @@ 986151F82B167A5B0083E68F /* eidos_symbol_table.cpp in Sources */, 986152332B167B4E0083E68F /* tdist.c in Sources */, 9861525D2B167B4E0083E68F /* gauss.c in Sources */, + 985C533F2EFB071900E51591 /* dirichlet.c in Sources */, 98EDB5172E65410C00CC8798 /* copy.c in Sources */, 98235686252FDCF50096A745 /* lodepng.cpp in Sources */, 986152292B167B4E0083E68F /* vector.c in Sources */, @@ -4313,7 +4313,7 @@ 98CEFCEF2AAFABAA00D2C9B4 /* spline2d.c in Sources */, 98C821661C7A983800548839 /* beta.c in Sources */, 985F3EFE24BA2F1500E712E0 /* eidos_test_functions_vector.cpp in Sources */, - 98C634452EF9F632003F12A3 /* dirichlet.c in Sources */, + 985C53442EFB071900E51591 /* dirichlet.c in Sources */, 98C821791C7A983800548839 /* psi.c in Sources */, 98CEFD8B2AAFB4F000D2C9B4 /* view.c in Sources */, 985724D31AD479010047C223 /* eidos_test.cpp in Sources */, @@ -4380,6 +4380,7 @@ 98CF5207294A3FC900557BBA /* eidos_rng.cpp in Sources */, 98CEFCF62AAFABAA00D2C9B4 /* bilinear.c in Sources */, 98CF5208294A3FC900557BBA /* eidos_beep.cpp in Sources */, + 985C53402EFB071900E51591 /* dirichlet.c in Sources */, 98CF5209294A3FC900557BBA /* GraphView.mm in Sources */, 98CF520A294A3FC900557BBA /* stream.c in Sources */, 98CEFD662AAFABAA00D2C9B4 /* accel.c in Sources */, @@ -4465,7 +4466,6 @@ 98CF524F294A3FC900557BBA /* population.cpp in Sources */, 98CF5250294A3FC900557BBA /* eidos_test_functions_vector.cpp in Sources */, 98CF5251294A3FC900557BBA /* eidos_class_Dictionary.cpp in Sources */, - 98C634472EF9F632003F12A3 /* dirichlet.c in Sources */, 98CF5252294A3FC900557BBA /* elementary.c in Sources */, 986070EC2AACECD600FD6143 /* spatial_kernel.cpp in Sources */, 98CF5254294A3FC900557BBA /* GraphView_FitnessOverTime.mm in Sources */, @@ -4668,7 +4668,7 @@ 98CEFCF02AAFABAA00D2C9B4 /* spline2d.c in Sources */, 98CF53EC294A714200557BBA /* beta.c in Sources */, 98CF53ED294A714200557BBA /* eidos_test_functions_vector.cpp in Sources */, - 98C634482EF9F632003F12A3 /* dirichlet.c in Sources */, + 985C53412EFB071900E51591 /* dirichlet.c in Sources */, 98CF53EE294A714200557BBA /* psi.c in Sources */, 98CEFD8C2AAFB4F000D2C9B4 /* view.c in Sources */, 98CF53EF294A714200557BBA /* eidos_test.cpp in Sources */, @@ -4735,6 +4735,7 @@ 98D4C1D61A6F541F00FFB083 /* eidos_rng.cpp in Sources */, 98CEFCF52AAFABAA00D2C9B4 /* bilinear.c in Sources */, 9893C7DF1CDA2FC10029AC94 /* eidos_beep.cpp in Sources */, + 985C533C2EFB071900E51591 /* dirichlet.c in Sources */, 986926D91AA140550000E138 /* GraphView.mm in Sources */, 98C821471C7A983700548839 /* stream.c in Sources */, 98CEFD652AAFABAA00D2C9B4 /* accel.c in Sources */, @@ -4820,7 +4821,6 @@ 98D4C1E31A6F544B00FFB083 /* population.cpp in Sources */, 985F3EFD24BA2F1500E712E0 /* eidos_test_functions_vector.cpp in Sources */, 989A5BEA2525304100E7192D /* eidos_class_Dictionary.cpp in Sources */, - 98C6344C2EF9F632003F12A3 /* dirichlet.c in Sources */, 98C821561C7A983700548839 /* elementary.c in Sources */, 986070EB2AACECD600FD6143 /* spatial_kernel.cpp in Sources */, 986926E81AA40AFF0000E138 /* GraphView_FitnessOverTime.mm in Sources */, @@ -4943,7 +4943,7 @@ 98D7D6622AB24C40002AFE34 /* tdist.c in Sources */, 98D7EB9728CE557C00DEAAC4 /* eidos_class_Image.cpp in Sources */, 98D7EB9828CE557C00DEAAC4 /* swap.c in Sources */, - 98C6344B2EF9F632003F12A3 /* dirichlet.c in Sources */, + 985C53422EFB071900E51591 /* dirichlet.c in Sources */, 98D7EB9928CE557C00DEAAC4 /* eidos_class_Dictionary.cpp in Sources */, 98D7EB9A28CE557C00DEAAC4 /* deflate.c in Sources */, 98D7EB9B28CE557C00DEAAC4 /* error.c in Sources */, @@ -5063,7 +5063,6 @@ 98D7ECA128CE58FC00DEAAC4 /* eidos_symbol_table.cpp in Sources */, 98D7ECA228CE58FC00DEAAC4 /* eidos_type_interpreter.cpp in Sources */, 98D7ECA328CE58FC00DEAAC4 /* eidos_call_signature.cpp in Sources */, - 98C634442EF9F632003F12A3 /* dirichlet.c in Sources */, 98CEFD172AAFABAA00D2C9B4 /* interp.c in Sources */, 98D7ECA428CE58FC00DEAAC4 /* dgemv.c in Sources */, 98D7ECA528CE58FC00DEAAC4 /* init.c in Sources */, @@ -5152,6 +5151,7 @@ 98D7D66C2AB24CBC002AFE34 /* chisq.c in Sources */, 98D7ECE428CE58FC00DEAAC4 /* minmax.c in Sources */, 98CEFD7C2AAFB12F00D2C9B4 /* cspline.c in Sources */, + 985C533D2EFB071900E51591 /* dirichlet.c in Sources */, 98EDB5192E65410C00CC8798 /* copy.c in Sources */, 986070ED2AACECD600FD6143 /* spatial_kernel.cpp in Sources */, 98D7ECE528CE58FC00DEAAC4 /* trees.c in Sources */, From 4460197ed9ecc5ee7a1fc2fa7c365491adab504a Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 25 Dec 2025 12:21:25 -0600 Subject: [PATCH 039/107] add Individual method demandPhenotype() --- QtSLiM/help/SLiMHelpClasses.html | 4 + SLiMgui/SLiMHelpClasses.rtf | 24 + VERSIONS | 1 + core/chromosome.cpp | 2 +- core/community.cpp | 37 +- core/community.h | 2 +- core/individual.cpp | 977 ++++++++++++++++++++++++++++++- core/individual.h | 17 + core/interaction_type.cpp | 2 +- core/mutation.cpp | 10 +- core/population.cpp | 6 +- core/slim_eidos_block.cpp | 8 +- core/slim_eidos_block.h | 1 + core/slim_globals.cpp | 1 + core/slim_globals.h | 2 + core/species.cpp | 28 +- core/species.h | 2 +- core/species_eidos.cpp | 23 +- core/subpopulation.cpp | 8 + core/subpopulation.h | 12 + core/trait.cpp | 44 +- core/trait.h | 6 +- 22 files changed, 1166 insertions(+), 51 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index b92276a8..e617674f 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -456,6 +456,10 @@

Returns a logical vector indicating whether each of the mutations in mutations is present in the individual (in any of its haplosomes); each element in the returned vector indicates whether the corresponding mutation is present (T) or absent (F).  This method is provided for speed; it is much faster than the corresponding Eidos code.

 (integer$)countOfMutationsOfType(io<MutationType>$ mutType)

Returns the number of mutations that are of the type specified by mutType, out of all of the mutations in the individual (in all of its haplosomes; a mutation that is present in both homologous haplosomes counts twice).  If you need a vector of the matching Mutation objects, rather than just a count, you should probably use mutationsFromHaplosomes().  This method is provided for speed; it is much faster than the corresponding Eidos code.

+

+ (void)demandPhenotype([Nio<Trait> trait = NULL], [logical$ forceRecalc = F])

+

Expresses “demand” for the phenotypes (trait values) of the trait(s) specified by trait, for the target vector of individuals.  This triggers the recalculation of those trait values in those individuals, as needed.  More specifically, if forceRecalc is F (the default), the specified trait values will only be recalculated if their current value is NAN; if they have any other value, they are considered to already be calculated, and will not be recalculated.  If forceRecalc is T, the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date.  No value is returned by demandPhenotype(), since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.

+

This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set.  Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual.  The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.

+

Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so demandPhenotype() does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.  See sections 24.6 and 25.3 for details on fitness recalculation.

– (object<Haplosome>)haplosomesForChromosomes([Niso<Chromosome> chromosomes = NULL], [Ni$ index = NULL], [logical$ includeNulls = T])

Returns a vector containing the haplosomes of the target individual that correspond to the chromosomes passed in chromosomes (following the order of the chromosomes property of Individual).  Chromosomes can be specified by id (integer), by symbol (string) or by the Chromosome objects themselves; if NULL is passed (the default), all chromosomes defined for the species are used, in the order in which they were defined.

For chromosomes that are intrinsically diploid (types "A", "X", and "Z", as well as the "H-" and "-Y" backward-compatibility chromosome types), index can be 0 or 1, requesting only the first or second haplosome, respectively, for that chromosome; for other chromosome types, index is ignored.  If includeNulls is T (the default), any null haplosomes corresponding to the specified chromosomes are included in the result; if it is F, null haplosomes are excluded.  See also the properties haplosomes, haplosomesNonNull, haploidGenome1, haploidGenome1NonNull, haploidGenome2, and haploidGenome2NonNull.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 18b832e7..82aa2d8c 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -3648,6 +3648,30 @@ See the description of the \f4\fs20 . This method is provided for speed; it is much faster than the corresponding Eidos code.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 +\'a0(void)demandPhenotype([Nio\'a0trait\'a0=\'a0NULL], [logical$\'a0forceRecalc\'a0=\'a0F])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Expresses \'93demand\'94 for the phenotypes (trait values) of the trait(s) specified by +\f3\fs18 trait +\f4\fs20 , for the target vector of individuals. This triggers the recalculation of those trait values in those individuals, as needed. More specifically, if +\f3\fs18 forceRecalc +\f4\fs20 is +\f3\fs18 F +\f4\fs20 (the default), the specified trait values will only be recalculated if their current value is +\f3\fs18 NAN +\f4\fs20 ; if they have any other value, they are considered to already be calculated, and will not be recalculated. If +\f3\fs18 forceRecalc +\f4\fs20 is +\f3\fs18 T +\f4\fs20 , the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date. No value is returned by +\f3\fs18 demandPhenotype() +\f4\fs20 , since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.\ +This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set. Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual. The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.\ +Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so +\f3\fs18 demandPhenotype() +\f4\fs20 does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way. See sections 24.6 and 25.3 for details on fitness recalculation.\ +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \'96\'a0(object)haplosomesForChromosomes([Niso\'a0chromosomes\'a0=\'a0NULL], [Ni$\'a0index\'a0=\'a0NULL], [logical$\'a0includeNulls\'a0=\'a0T])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 diff --git a/VERSIONS b/VERSIONS index 97e66fec..3ee65afc 100644 --- a/VERSIONS +++ b/VERSIONS @@ -108,6 +108,7 @@ multitrait branch: allow a singleton migration rate value, applied for all subpops given allow the destination subpop to be given as a source, iff all rates supplied are 0.0, to stop all migration: allSubpops.setMigrationRates(allSubpops, 0.0) fix #567, plot windows can have their aspect ratio distorted due to screen size and other constraints + add Individual class method +(void)demandPhenotype([Nio trait = NULL], [l$ forceRecalc = F]) version 5.1 (Eidos version 4.1): diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 955b5a42..3e54bada 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1773,7 +1773,7 @@ void Chromosome::DrawBreakpoints(Individual *p_parent, Haplosome *p_haplosome1, { parent_sex = p_parent->sex_; parent_subpop = p_parent->subpopulation_; - recombination_callbacks = species_.CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, parent_subpop->subpopulation_id_, id_); + recombination_callbacks = species_.CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, parent_subpop->subpopulation_id_, -1, id_); // SPECIES CONSISTENCY CHECK if (&p_parent->subpopulation_->species_ != &species_) diff --git a/core/community.cpp b/core/community.cpp index 1abbdbcb..e7094415 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -556,7 +556,7 @@ void Community::ValidateScriptBlockCaches(void) } } -std::vector Community::ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, int64_t p_chromosome_id, Species *p_species) +std::vector Community::ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_objectid_t p_trait_index, int64_t p_chromosome_id, Species *p_species) { if (!script_block_types_cached_) ValidateScriptBlockCaches(); @@ -638,6 +638,15 @@ std::vector Community::ScriptBlocksMatching(slim_tick_t p_tick, continue; } + // check that the trait index matches, if requested + if (p_trait_index != -1) + { + slim_objectid_t trait_index = script_block->trait_index_; + + if ((trait_index != -1) && (p_trait_index != trait_index)) + continue; + } + // check that the chromosome id matches, if requested if (p_chromosome_id != -1) { @@ -1006,6 +1015,13 @@ void Community::AddScriptBlock(SLiMEidosBlock *p_script_block, EidosInterpreter EIDOS_TERMINATION << "ERROR (Community::AddScriptBlock): script block is specific to a subpopulation id (" << p_script_block->subpopulation_id_ << ") that belongs to a different species." << EidosTerminate(p_error_token); } + if (p_script_block->trait_index_ >= 0) + { + // if the trait index is specified, we check that it is in range for the specified species + if (p_script_block->trait_index_ >= p_script_block->species_spec_->TraitCount()) + EIDOS_TERMINATION << "ERROR (Community::AddScriptBlock): script block is specific to a trait index that is out of range for the species." << EidosTerminate(p_error_token); + } + if (p_script_block->interaction_type_id_ >= 0) { // interaction() callbacks may not have a specified species @@ -1040,6 +1056,9 @@ void Community::AddScriptBlock(SLiMEidosBlock *p_script_block, EidosInterpreter if (p_script_block->subpopulation_id_ != -1) EIDOS_TERMINATION << "ERROR (Community::AddScriptBlock): (internal error) script block for a non-callback or initialize() callback has subpopulation_id_ set." << EidosTerminate(p_error_token); + if (p_script_block->trait_index_ != -1) + EIDOS_TERMINATION << "ERROR (Community::AddScriptBlock): (internal error) script block for a non-callback or initialize() callback has trait_index_ set." << EidosTerminate(p_error_token); + if (p_script_block->chromosome_id_ != -1) EIDOS_TERMINATION << "ERROR (Community::AddScriptBlock): (internal error) script block for a non-callback or initialize() callback has chromosome_id_ set." << EidosTerminate(p_error_token); @@ -2272,7 +2291,7 @@ void Community::AllSpecies_RunInitializeCallbacks(void) // The zero tick is handled here by shared code, since it is the same for WF and nonWF models // execute user-defined function blocks first; no need to profile this, it's just the definitions not the executions - std::vector function_blocks = ScriptBlocksMatching(-1, SLiMEidosBlockType::SLiMEidosUserDefinedFunction, -1, -1, -1, -1, nullptr); + std::vector function_blocks = ScriptBlocksMatching(-1, SLiMEidosBlockType::SLiMEidosUserDefinedFunction, -1, -1, -1, -1, -1, nullptr); for (auto script_block : function_blocks) ExecuteFunctionDefinitionBlock(script_block); @@ -2372,7 +2391,7 @@ void Community::RunInitializeCallbacks(void) num_modeltype_declarations_ = 0; // execute `species all` initialize() callbacks, which should always have a tick of 0 set - std::vector init_blocks = ScriptBlocksMatching(0, SLiMEidosBlockType::SLiMEidosInitializeCallback, -1, -1, -1, -1, nullptr); + std::vector init_blocks = ScriptBlocksMatching(0, SLiMEidosBlockType::SLiMEidosInitializeCallback, -1, -1, -1, -1, -1, nullptr); for (auto script_block : init_blocks) ExecuteEidosEvent(script_block); @@ -2625,7 +2644,7 @@ bool Community::_RunOneTickWF(void) #endif cycle_stage_ = SLiMCycleStage::kWFStage0ExecuteFirstScripts; - std::vector first_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventFirst, -1, -1, -1, -1, nullptr); + std::vector first_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventFirst, -1, -1, -1, -1, -1, nullptr); for (auto script_block : first_blocks) ExecuteEidosEvent(script_block); @@ -2657,7 +2676,7 @@ bool Community::_RunOneTickWF(void) #endif cycle_stage_ = SLiMCycleStage::kWFStage1ExecuteEarlyScripts; - std::vector early_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventEarly, -1, -1, -1, -1, nullptr); + std::vector early_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventEarly, -1, -1, -1, -1, -1, nullptr); for (auto script_block : early_blocks) ExecuteEidosEvent(script_block); @@ -2822,7 +2841,7 @@ bool Community::_RunOneTickWF(void) #endif cycle_stage_ = SLiMCycleStage::kWFStage5ExecuteLateScripts; - std::vector late_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventLate, -1, -1, -1, -1, nullptr); + std::vector late_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventLate, -1, -1, -1, -1, -1, nullptr); for (auto script_block : late_blocks) ExecuteEidosEvent(script_block); @@ -2988,7 +3007,7 @@ bool Community::_RunOneTickNonWF(void) #endif cycle_stage_ = SLiMCycleStage::kNonWFStage0ExecuteFirstScripts; - std::vector first_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventFirst, -1, -1, -1, -1, nullptr); + std::vector first_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventFirst, -1, -1, -1, -1, -1, nullptr); for (auto script_block : first_blocks) ExecuteEidosEvent(script_block); @@ -3124,7 +3143,7 @@ bool Community::_RunOneTickNonWF(void) #endif cycle_stage_ = SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts; - std::vector early_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventEarly, -1, -1, -1, -1, nullptr); + std::vector early_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventEarly, -1, -1, -1, -1, -1, nullptr); for (auto script_block : early_blocks) ExecuteEidosEvent(script_block); @@ -3285,7 +3304,7 @@ bool Community::_RunOneTickNonWF(void) #endif cycle_stage_ = SLiMCycleStage::kNonWFStage6ExecuteLateScripts; - std::vector late_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventLate, -1, -1, -1, -1, nullptr); + std::vector late_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventLate, -1, -1, -1, -1, -1, nullptr); for (auto script_block : late_blocks) ExecuteEidosEvent(script_block); diff --git a/core/community.h b/core/community.h index fd9f3510..e1e6fe64 100644 --- a/core/community.h +++ b/core/community.h @@ -213,7 +213,7 @@ class Community : public EidosDictionaryUnretained // Managing script blocks; these two methods should be used as a matched pair, bracketing each cycle stage that calls out to script void ValidateScriptBlockCaches(void); - std::vector ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, int64_t p_chromosome_id, Species *p_species); + std::vector ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_objectid_t p_trait_index, int64_t p_chromosome_id, Species *p_species); std::vector &AllScriptBlocks(); std::vector AllScriptBlocksForSpecies(Species *p_species); void OptimizeScriptBlock(SLiMEidosBlock *p_script_block); diff --git a/core/individual.cpp b/core/individual.cpp index 72c72ed1..f794e883 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -145,7 +145,7 @@ void Individual::_InitializePerTraitInformation(void) #endif trait_info_ = &trait_info_0_; - trait_info_0_.phenotype_ = 0.0; + trait_info_0_.phenotype_ = std::numeric_limits::quiet_NaN(); // "uncalculated" trait_info_0_.offset_ = traits[0]->DrawIndividualOffset(); } else if (trait_count == 0) @@ -178,7 +178,7 @@ void Individual::_InitializePerTraitInformation(void) for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - trait_info_[trait_index].phenotype_ = 0.0; + trait_info_[trait_index].phenotype_ = std::numeric_limits::quiet_NaN(); // "uncalculated" trait_info_[trait_index].offset_ = traits[trait_index]->DrawIndividualOffset(); } } @@ -4265,6 +4265,7 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_haplosomesForChromosomes, kEidosValueMaskObject, gSLiM_Haplosome_Class))->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddInt_OSN("index", gStaticEidosValueNULL)->AddLogical_OS("includeNulls", gStaticEidosValue_LogicalT)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_phenotypeForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_demandPhenotype, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setPhenotypeForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddNumeric("phenotype")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); @@ -4287,6 +4288,7 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ { switch (p_method_id) { + case gID_demandPhenotype: return ExecuteMethod_demandPhenotype(p_method_id, p_target, p_arguments, p_interpreter); case gID_setOffsetForTrait: return ExecuteMethod_setOffsetForTrait(p_method_id, p_target, p_arguments, p_interpreter); case gID_setPhenotypeForTrait: return ExecuteMethod_setPhenotypeForTrait(p_method_id, p_target, p_arguments, p_interpreter); case gID_outputIndividuals: return ExecuteMethod_outputIndividuals(p_method_id, p_target, p_arguments, p_interpreter); @@ -5796,6 +5798,977 @@ EidosValue_SP Individual_Class::ExecuteMethod_setSpatialPosition(EidosGlobalStri } +// +// Phenotype demand +// +#pragma mark - +#pragma mark Phenotype demand +#pragma mark - + +// ********************* + (void)demandPhenotype([Nio trait = NULL], [l$ forceRecalc = F]) +// +EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *forceRecalc_value = p_arguments[1].get(); + + int individuals_count = p_target->Count(); + + if (individuals_count == 0) + return gStaticEidosValue_Float_ZeroVec; + + Individual **individuals_buffer = (Individual **)p_target->ObjectData(); + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForIndividuals(p_target); + + if (!species) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_demandPhenotype): demandPhenotype() requires that all individuals belong to the same species." << EidosTerminate(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "demandPhenotype"); + int trait_count = (int)trait_indices.size(); + + if (trait_count == 0) + return gStaticEidosValue_Float_ZeroVec; + + // forceRecalc + eidos_logical_t forceRecalc = forceRecalc_value->LogicalAtIndex_NOCAST(0, nullptr); + + if (forceRecalc) + DemandPhenotype(species, individuals_buffer, individuals_count, trait_indices); + else + DemandPhenotype(species, individuals_buffer, individuals_count, trait_indices); + + // BCH 12/25/2025: I considered having this return the trait values that were demanded; but I think void is + // better. Collecting the trait values would be additional work here that would not always be desired, so + // it's better to make the user do it separately if they want it. Also, this method should generally be + // called once to demand a set of traits, but getting a whole set of trait values back -- as an interleaved + // vector or a matrix -- is pretty inconvenient to work with. + return gStaticEidosValueVOID; +} + +template +void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const +{ + // Given a vector of individuals that are all guaranteed to belong to the provided species, and a vector of + // trait indices guaranteed to be of length 1 or longer, this method loops over the chromosomes of the + // species (the top-level loop to make mutation run experiment timing simple), then over the traits provided + // (to avoid having to test for additive vs. multiplicative over and over for each mutation), then over + // the individuals (the level at which parallelization of the code occurs). For each individual, it + // dispatches to a sub-method that computes the trait value produced by all of the mutations for the given + // chromosome/trait/individual. This method then aggregates those values together to produce the final + // trait values, which are saved into the phenotype storage of each individual. + + // First we cache a vector of mutationEffect() callbacks for each subpop; we do this here, rather than at the + // start of each tick, so that newly registered callbacks function, and the current active state of each + // callback is respected. + std::vector mutationEffect_callbacks = species->CallbackBlocksMatching(species->community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1); + Population &population = species->population_; + bool has_active_callbacks = false; + int trait_indices_count = (int)trait_indices.size(); + + for (SLiMEidosBlock *callback : mutationEffect_callbacks) + { + if (callback->block_active_) + { + has_active_callbacks = true; + break; + } + } + +#if DEBUG + // check that our subpopulation per-trait caches are correctly set up for each trait + for (std::pair &subpop_pair : population.subpops_) + { + Subpopulation *subpop = subpop_pair.second; + + if ((int)subpop->per_trait_subpop_caches_.size() != species->TraitCount()) + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ is not correctly sized." << EidosTerminate(); + + for (int trait_index = 0; trait_index < species->TraitCount(); trait_index++) + { + Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; + + if (subpop_trait_caches.mutationEffect_callbacks_per_trait.size() != 0) + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ mutationEffect_callbacks_per_trait_ is not empty." << EidosTerminate(); + if (subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED != nullptr) + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ IncorporateEffects_Haploid_TEMPLATED is not nullptr." << EidosTerminate(); + if (subpop_trait_caches.IncorporateEffects_Hemizygous_TEMPLATED != nullptr) + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ IncorporateEffects_Hemizygous_TEMPLATED is not nullptr." << EidosTerminate(); + if (subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED != nullptr) + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ IncorporateEffects_Diploid_TEMPLATED is not nullptr." << EidosTerminate(); + } + } +#endif + + // Next we cache method pointers for haploid and diploid chromosomes, which we will use throughout. These + // are templated for efficiency, so we have to choose the correct template. That depends on the subpopulation + // since each subpopulation might have a different set of mutationEffect() callbacks. Note the template + // for _IncorporateEffects_Haploid() (which handles both haploid and hemizygous cases): + // + // template + // + // and the template for _IncorporateEffects_Diploid() (which handles the non-hemizygous diploid case): + // + // template + // + if (has_active_callbacks) + { + // if we have any active callbacks, we have to account for all callbacks (active or not), since + // one callback might activate/deactivate another; inactive callbacks might not stay inactive + // This callback applies to this subpopulation. We now need to determine which traits, if any, it applies to. + // For each trait we keep a separate vector of callbacks that apply to that trait. + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + int64_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; + TraitType traitType = trait->Type(); + + for (std::pair &subpop_pair : population.subpops_) + { + Subpopulation *subpop = subpop_pair.second; + Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; + + std::vector &subpop_per_trait_mutationEffect_callbacks = subpop_trait_caches.mutationEffect_callbacks_per_trait; + auto &IncorporateEffects_Haploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED; + auto &IncorporateEffects_Hemizygous_TEMPLATED = subpop_trait_caches.IncorporateEffects_Hemizygous_TEMPLATED; + auto &IncorporateEffects_Diploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED; + + for (SLiMEidosBlock *callback : mutationEffect_callbacks) + { + // check if this callback applies to this subpopulation + slim_objectid_t callback_subpop_id = callback->subpopulation_id_; + + if ((callback_subpop_id == -1) || (callback_subpop_id == subpop->subpopulation_id_)) + { + // check if this callback applies to this trait + int64_t callback_trait_index = callback->trait_index_; + + if ((callback_trait_index == -1) || (callback_trait_index == trait_index)) + subpop_per_trait_mutationEffect_callbacks.emplace_back(callback); + } + } + + int mutationEffect_callback_count = (int)subpop_per_trait_mutationEffect_callbacks.size(); + bool mutationEffect_callbacks_exist = (mutationEffect_callback_count > 0); + bool single_mutationEffect_callback = (mutationEffect_callback_count == 1); + + // cache a pointer to the correct implementation of IncorporateEffects_X() for the subpopulation + // this varies by subpopulation because of the different callbacks that might be present + if (!mutationEffect_callbacks_exist) + { + if (traitType == TraitType::kAdditive) + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + else + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + } + else if (single_mutationEffect_callback) + { + if (traitType == TraitType::kAdditive) + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + else + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + } + else + { + if (traitType == TraitType::kAdditive) + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + else + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + } + } + } + } + else + { + // if we have no active callbacks at all, we know that that will remain true across the operation + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + int64_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; + TraitType traitType = trait->Type(); + + for (std::pair &subpop_pair : population.subpops_) + { + Subpopulation *subpop = subpop_pair.second; + Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; + + auto &IncorporateEffects_Haploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED; + auto &IncorporateEffects_Hemizygous_TEMPLATED = subpop_trait_caches.IncorporateEffects_Hemizygous_TEMPLATED; + auto &IncorporateEffects_Diploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED; + + // cache a pointer to the correct implementation of IncorporateEffects_X() for the subpopulation, given no callbacks + if (traitType == TraitType::kAdditive) + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + else + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + } + } + } + + // FIXME MULTITRAIT: We need to recache non-neutral caches for all mutation runs here when running parallel. + // This is maybe also where we would recache the total phenotypic effect of any mutation runs for traits with + // "independent dominance". +#warning recache non-neutral caches first + + // FIXME MULTITRAIT BCH 12/25/2025: For now we disable the non-neutral caches. To enable them we'd need to + // deal with the "regime" stuff that Population::RecalculateFitness() does, and I think that probably all + // needs to get redesigned, so I'm not going to try to get it working here for now. +#define SLIM_USE_NONNEUTRAL_CACHES 0 + + // For a given individual, for a given trait, we have to make a decision as to whether we will recalculate or not. That decision gets made + // once and then holds across all chromosomes for the individual. But we're looping over chromosomes at the topmost level, so we have a + // little problem: how will we remember whether we decided to recalculate a given individual/trait when we get to doing the work for + // successive chromosomes? We have to keep a vector of flags, actually; there's no choice but to keep that state somewhere. So, we use + // std::vector with # individuals x # traits flags in it. Here we loop through individuals and traits, making decisions about whether + // we're recalculating each trait in each individual. For f_force_recalc == true that decision is always YES, so this method is templated + // to avoid all overhead completely in that case. For each phenotype that we do intend to recalculate, we set its initial value from the + // baseline offset and individual offset for the trait. + // FIXME MULTITRAIT: if we have just one chromosome and one trait, we don't need this std::vector, because the decision is only needed + // once per individual; that should probably be special-cased, since this bool vector thing is gross and heavyweight. + std::vector recalc_decisions; + + if (!f_force_recalc) + recalc_decisions.resize(individuals_count * trait_indices.size()); // zero-fills to false + + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + int64_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; + TraitType traitType = trait->Type(); + slim_effect_t trait_baseline_offset = trait->BaselineOffset(); + + if (traitType == TraitType::kAdditive) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; + + if (f_force_recalc) + { + trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + } + else if (!f_force_recalc && std::isnan(trait_info.phenotype_)) + { + recalc_decisions[individual_index * trait_indices_count + trait_indices_index] = true; + trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + } + // else (!f_force_recalc && !std::isnan(trait_info.phenotype_)), so we are not recalculating + } + } + else // (traitType == TraitType::kMultiplicative) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; + + if (f_force_recalc) + { + trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + } + else if (!f_force_recalc && std::isnan(trait_info.phenotype_)) + { + recalc_decisions[individual_index * trait_indices_count + trait_indices_index] = true; + trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + } + // else (!f_force_recalc && !std::isnan(trait_info.phenotype_)), so we are not recalculating + } + } + } + + // calculate the specified phenotypes for the individual; this loops through all chromosomes, handling ploidy and callbacks as needed + // it is very nice to have the top-level loop be over the chromosomes, so that each one can do a single timing for mutrun experiments + int haplosome_index = 0; + + for (Chromosome *chromosome : species->Chromosomes()) + { + if (species->DoingAnyMutationRunExperiments()) + chromosome->StartMutationRunExperimentClock(); + + switch (chromosome->Type()) + { + // diploid, possibly with one or both being null haplosomes + case ChromosomeType::kA_DiploidAutosome: + case ChromosomeType::kX_XSexChromosome: + case ChromosomeType::kZ_ZSexChromosome: + { + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + int64_t trait_index = trait_indices[trait_indices_index]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + Subpopulation *subpop = ind->subpopulation_; + Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; + std::vector &subpop_trait_mutationEffect_callbacks = subpop_trait_caches.mutationEffect_callbacks_per_trait; + Haplosome *haplosome1 = ind->haplosomes_[haplosome_index]; + Haplosome *haplosome2 = ind->haplosomes_[haplosome_index+1]; + + if (haplosome1->IsNull()) + { + if (!haplosome2->IsNull()) + { + // hemizygous (haplosome2) + auto IncorporateEffects_Hemizygous_TEMPLATED = subpop_trait_caches.IncorporateEffects_Hemizygous_TEMPLATED; + (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome2, trait_index, subpop_trait_mutationEffect_callbacks); + } + else + { + // both haplosomes are null (only happens with chromosome type "A"; no work to be done + } + } + else if (haplosome2->IsNull()) + { + // hemizygous (haplosome1) + auto IncorporateEffects_Hemizygous_TEMPLATED = subpop_trait_caches.IncorporateEffects_Hemizygous_TEMPLATED; + (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome1, trait_index, subpop_trait_mutationEffect_callbacks); + } + else + { + // FIXME MULTITRAIT: with no callbacks, check for the "independent dominance" case here and short-circuit the calculations for all individuals + // to just pull pre-calculated values from the mutation runs of the individual; can that just be another templated variant, actually? + // NOTE: when a given mutation run is hemizygous the "independent dominance" cache can be used, even if the mutations in it + // would not be "independent dominance" when found in the diploid state. Similarly, haploid chromosomes are ALWAYS in an + // "independent dominance" state, and should always use those caches. So that mechanism is actually quite general, and needs + // to be incorporated broadly into this design! When do those cached values get calculated, when do they get used, and how will + // that interact with parallelization? This depends somewhat on non-neutral caching; we might want separate non-neutral caches + // for mutations that are vs. are not "independent dominance", and so we might want a flag on Mutation that indicates that, for + // fast sorting of mutations into the correct caches. For haplosomes that are associated with a haploid chromosome, these caches + // would be the same (all mutations are independent dominance); for haplosomes associated with a diploid chromosome, these caches + // would be different and non-intersecting, and here we would assimilate the "independent dominance" effect for all mutruns in the + // two haplosomes, and then assimilate the effects of all non-neutral mutations in the non-independent non-neutral caches. + + // FIXME MULTITRAIT: I think in the parallel case we want to just loop through all mutation runs and set up these caches ahead of + // time. To do that efficiently we should have a vector of all mutation runs for a species or a chromosome (do we have that?). + // We should also have a flag for whether any non-neutral independent-dominance mutations have ever been seen for a given + // chromosome; if not, we can skip making that cache, or even checking that per-mutation flag. It should also go the other way: + // if we have never seen a non-neutral mutation that is *not* independent dominance, we can skip making that cache or checking + // those flags. This method should maybe be templated based upon those two flags, so that it can skip thinking about one or the + // other category of mutations completely; otherwise, we would branch twice per individual, which is probably not a big deal. + + // FIXME MULTITRAIT: Ideally, all of these mechanics could be per-trait, such that maybe one trait exhibits independent dominance + // for all of its effects, and that fact gets leveraged for its calculations, whereas another trait exhibits all non-independence + // for all of its effects, and that also gets leveraged. I need to think about whether that requires separate mutrun caches for + // each trait, or whether we can get some leverage on this with just the two planned caches across all traits. I think it works + // with just the two caches? Hmm, but if the effect of a mutation is neutral for one trait and non-neutral for another, we have + // to put it in the non-neutral cache; and if the effect of a mutation is independent-dominance for one trait and not for another, + // we have to put it in the non-independent cache. So there's a lowest-common-denominator thing happening here. The only way to + // avoid that is to have separate non-neutral caches per trait, for each mutation run. Which is not out of the question if it + // allows a big speedup, skipping work for particular mutation runs for particular traits. This is a big design decision to be made. + + // FIXME MULTITRAIT: Note that the "independent dominance" optimization only works when mutationEffect() callbacks are not present. + // When they are present, rather than calling e.g. IncorporateEffects_Independent() for each haplosome, we would just call + // IncorporateEffects_Diploid() again for the independent-dominance caches as well, in some TBD manner; we would treat those + // mutations identically to the non-independent mutations, since the mutationEffect() callbacks might make them non-independent. + + // FIXME MULTITRAIT: We will want smarter cache-invalidation for these mutrun caches. Whenever a mutation is added or removed from + // a mutrun, its caches invalidate EXCEPT if the mutation being added/removed is neutral, in which case it doesn't affect the caches + // (and if it is non-neutral, it should affect only the cache it would be a member of). Whenever a mutation's effect or dominance + // change, all mutruns for that chromosome in that positional range should invalidate (since they might contain that mutation), + // EXCEPT if the mutation is known not to belong to any mutrun, which would be true for `mut` inside a mutation() callback; we need + // to be smart about that to avoid unnecessary cache invalidations. + + // diploid, both haplosomes non-null + auto IncorporateEffects_Diploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED; + (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait_index, subpop_trait_mutationEffect_callbacks); + } + } + } + + haplosome_index += 2; + break; + } + + // haploid, possibly null + case ChromosomeType::kH_HaploidAutosome: + case ChromosomeType::kY_YSexChromosome: + case ChromosomeType::kW_WSexChromosome: + case ChromosomeType::kHF_HaploidFemaleInherited: + case ChromosomeType::kFL_HaploidFemaleLine: + case ChromosomeType::kHM_HaploidMaleInherited: + case ChromosomeType::kML_HaploidMaleLine: + { + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + int64_t trait_index = trait_indices[trait_indices_index]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + Haplosome *haplosome = ind->haplosomes_[haplosome_index]; + + if (haplosome->IsNull()) + continue; + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + Subpopulation *subpop = ind->subpopulation_; + Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; + std::vector &subpop_trait_mutationEffect_callbacks = subpop_trait_caches.mutationEffect_callbacks_per_trait; + auto IncorporateEffects_Haploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED; + + (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_trait_mutationEffect_callbacks); + } + } + + haplosome_index += 1; + break; + } + + // haploid special cases that have an accompanying null haplosome for backward compatibility + case ChromosomeType::kHNull_HaploidAutosomeWithNull: + case ChromosomeType::kNullY_YSexChromosomeWithNull: + { + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + int64_t trait_index = trait_indices[trait_indices_index]; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + Haplosome *haplosome = ind->haplosomes_[haplosome_index + ((chromosome->Type() == ChromosomeType::kNullY_YSexChromosomeWithNull) ? 1 : 0)]; + + if (haplosome->IsNull()) + continue; + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + Subpopulation *subpop = ind->subpopulation_; + Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; + std::vector &subpop_trait_mutationEffect_callbacks = subpop_trait_caches.mutationEffect_callbacks_per_trait; + auto IncorporateEffects_Haploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED; + + (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_trait_mutationEffect_callbacks); + } + } + + haplosome_index += 2; + break; + } + } + + if (species->DoingAnyMutationRunExperiments()) + chromosome->StopMutationRunExperimentClock("DemandPhenotype()"); + } + + // clear out each subpopulation's per-trait caches that we set up above; these are only for our private use + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + int64_t trait_index = trait_indices[trait_indices_index]; + + for (std::pair &subpop_pair : population.subpops_) + { + Subpopulation *subpop = subpop_pair.second; + Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; + + subpop_trait_caches.mutationEffect_callbacks_per_trait.clear(); + subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED = nullptr; + subpop_trait_caches.IncorporateEffects_Hemizygous_TEMPLATED = nullptr; + subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED = nullptr; + } + } +} + +template void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; +template void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; + + +// Low-level method to calculate a phenotype for one individual, for one haploid (or hemizygous) chromosome, +// for one trait. This will put the result of the calculation into the individual's phenotype information. +// This is called by Individual_Class::DemandPhenotype(), which loops over chromosomes, traits, and individuals. +template +void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks) +{ +#if DEBUG + // This method assumes that haplosome is not a null haplosome; the caller needs to guarantee this + if (haplosome->IsNull()) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) null haplosome." << EidosTerminate(); +#endif + + // we just need to scan through the haplosome and account for its mutations, using the homozygous mutation + // effect (no dominance effects with haploidy), or the hemizygous mutation effect for f_hemizygous == true +#if SLIM_USE_NONNEUTRAL_CACHES + int32_t nonneutral_change_counter = species->nonneutral_change_counter_; + int32_t nonneutral_regime = species->last_nonneutral_regime_; +#endif + + // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast + MutationType *single_callback_mut_type; + + if (f_singlecallback) + { + // our caller already did this lookup, to select this case, so this lookup is guaranteed to succeed + slim_objectid_t mutation_type_id = p_mutationEffect_callbacks[0]->mutation_type_id_; + + single_callback_mut_type = species->MutationTypeWithID(mutation_type_id); + } + + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + const int32_t mutrun_count = haplosome->mutrun_count_; + slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + for (int run_index = 0; run_index < mutrun_count; ++run_index) + { + const MutationRun *mutrun = haplosome->mutruns_[run_index]; + +#if SLIM_USE_NONNEUTRAL_CACHES + // Cache non-neutral mutations and read from the non-neutral buffers + const MutationIndex *haplosome_iter, *haplosome_max; + + mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); +#else + // Read directly from the MutationRun buffers + const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); + const MutationIndex *haplosome_max = mutrun->end_pointer_const(); +#endif + + // scan the mutation run and apply mutation effects + while (haplosome_iter != haplosome_max) + { + MutationIndex haplosome_mutation = *haplosome_iter++; + Mutation *mutation = (mut_block_ptr + haplosome_mutation); + slim_effect_t effect = (f_hemizygous ? mutation_block->TraitInfoForIndex(haplosome_mutation)[trait_index].hemizygous_effect_ : + mutation_block->TraitInfoForIndex(haplosome_mutation)[trait_index].homozygous_effect_); + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + + effect_accumulator += effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + + if (effect <= 0.0) { // not clipped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= effect; + } + } + } + + trait_info_[trait_index].phenotype_ = effect_accumulator; +} + +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); + +// Low-level method to calculate a phenotype for one individual, for one diploid chromosome, for one trait. +// This will put the result of the calculation into the individual's phenotype information. This is called +// by Individual_Class::DemandPhenotype(), which loops over chromosomes, traits, and individuals. +template +void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks) +{ +#if DEBUG + // This method assumes that haplosome1 and haplosome2 are not null; the caller needs to guarantee this + if (haplosome1->IsNull() || haplosome2->IsNull()) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) null haplosome." << EidosTerminate(); +#endif + + // both haplosomes are non-null, so we need to scan through and figure out which mutations are + // heterozygous and which are homozygous, and assign effects accordingly +#if SLIM_USE_NONNEUTRAL_CACHES + int32_t nonneutral_change_counter = species->nonneutral_change_counter_; + int32_t nonneutral_regime = species->last_nonneutral_regime_; +#endif + + // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast + MutationType *single_callback_mut_type; + + if (f_singlecallback) + { + // our caller already did this lookup, to select this case, so this lookup is guaranteed to succeed + slim_objectid_t mutation_type_id = p_mutationEffect_callbacks[0]->mutation_type_id_; + + single_callback_mut_type = species->MutationTypeWithID(mutation_type_id); + } + + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + const int32_t mutrun_count = haplosome1->mutrun_count_; + slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + for (int run_index = 0; run_index < mutrun_count; ++run_index) + { + const MutationRun *mutrun1 = haplosome1->mutruns_[run_index]; + const MutationRun *mutrun2 = haplosome2->mutruns_[run_index]; + +#if SLIM_USE_NONNEUTRAL_CACHES + // Cache non-neutral mutations and read from the non-neutral buffers + const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; + + mutrun1->beginend_nonneutral_pointers(mut_block_ptr, &haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); + mutrun2->beginend_nonneutral_pointers(mut_block_ptr, &haplosome2_iter, &haplosome2_max, nonneutral_change_counter, nonneutral_regime); +#else + // Read directly from the MutationRun buffers + const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); + const MutationIndex *haplosome2_iter = mutrun2->begin_pointer_const(); + + const MutationIndex *haplosome1_max = mutrun1->end_pointer_const(); + const MutationIndex *haplosome2_max = mutrun2->end_pointer_const(); +#endif + + // first, handle the situation before either haplosome iterator has reached the end of its haplosome, for simplicity/speed + if ((haplosome1_iter != haplosome1_max) && (haplosome2_iter != haplosome2_max)) + { + MutationIndex haplosome1_mutindex = *haplosome1_iter, haplosome2_mutindex = *haplosome2_iter; + slim_position_t haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_, haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + + do + { + if (haplosome1_iter_position < haplosome2_iter_position) + { + // Process a mutation in haplosome1 since it is leading + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; + + if (f_additive_trait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + + if (++haplosome1_iter == haplosome1_max) + break; + else { + haplosome1_mutindex = *haplosome1_iter; + haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; + } + } + else if (haplosome1_iter_position > haplosome2_iter_position) + { + // Process a mutation in haplosome2 since it is leading + Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; + + if (f_additive_trait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + + if (++haplosome2_iter == haplosome2_max) + break; + else { + haplosome2_mutindex = *haplosome2_iter; + haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + } + } + else + { + // Look for homozygosity: haplosome1_iter_position == haplosome2_iter_position + slim_position_t position = haplosome1_iter_position; + const MutationIndex *haplosome1_start = haplosome1_iter; + + // advance through haplosome1 as long as we remain at the same position, handling one mutation at a time + do + { + const MutationIndex *haplosome2_matchscan = haplosome2_iter; + + // advance through haplosome2 with haplosome2_matchscan, looking for a match for the current mutation in haplosome1, to determine whether we are homozygous or not + while (haplosome2_matchscan != haplosome2_max && (mut_block_ptr + *haplosome2_matchscan)->position_ == position) + { + if (haplosome1_mutindex == *haplosome2_matchscan) + { + // a match was found, so we multiply our fitness by the full homozygous effect + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t homozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].homozygous_effect_; + + if (f_additive_trait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += homozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (homozygous_effect <= 0.0) { // not clipped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= homozygous_effect; + } + goto homozygousExit1; + } + + haplosome2_matchscan++; + } + + // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient + { + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; + + if (f_additive_trait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + + homozygousExit1: + + if (++haplosome1_iter == haplosome1_max) + break; + else { + haplosome1_mutindex = *haplosome1_iter; + haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; + } + } while (haplosome1_iter_position == position); + + // advance through haplosome2 as long as we remain at the same position, handling one mutation at a time + do + { + const MutationIndex *haplosome1_matchscan = haplosome1_start; + + // advance through haplosome1 with haplosome1_matchscan, looking for a match for the current mutation in haplosome2, to determine whether we are homozygous or not + while ((haplosome1_matchscan != haplosome1_max) && ((mut_block_ptr + *haplosome1_matchscan)->position_ == position)) + { + if (haplosome2_mutindex == *haplosome1_matchscan) + { + // a match was found; we know this match was already found by the haplosome1 loop above, so our fitness has already been multiplied appropriately + goto homozygousExit2; + } + + haplosome1_matchscan++; + } + + // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient + { + Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; + + if (f_additive_trait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + + homozygousExit2: + + if (++haplosome2_iter == haplosome2_max) + break; + else { + haplosome2_mutindex = *haplosome2_iter; + haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + } + } while (haplosome2_iter_position == position); + + // break out if either haplosome has reached its end + if (haplosome1_iter == haplosome1_max || haplosome2_iter == haplosome2_max) + break; + } + } while (true); + } + + // one or the other haplosome has now reached its end, so now we just need to handle the remaining mutations in the unfinished haplosome +#if DEBUG + assert(!(haplosome1_iter != haplosome1_max && haplosome2_iter != haplosome2_max)); +#endif + + // if haplosome1 is unfinished, finish it + while (haplosome1_iter != haplosome1_max) + { + MutationIndex haplosome1_mutindex = *haplosome1_iter++; + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; + + if (f_additive_trait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + + // if haplosome2 is unfinished, finish it + while (haplosome2_iter != haplosome2_max) + { + MutationIndex haplosome2_mutindex = *haplosome2_iter++; + Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; + + if (f_additive_trait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + } + + trait_info_[trait_index].phenotype_ = effect_accumulator; +} + +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); + diff --git a/core/individual.h b/core/individual.h index 6f3d5d33..8c3fbc98 100644 --- a/core/individual.h +++ b/core/individual.h @@ -404,6 +404,15 @@ class Individual : public EidosDictionaryUnretained static bool s_any_haplosome_tag_set_; static bool s_any_individual_fitness_scaling_set_; + // phenotype demand for a single trait in a single individual, across a single chromosome; the result is + // accumulated into the trait value of the focal individual, which must be set up with an initial value + // see also the method DemandPhenotype() in class Individual_Class, which calls these methods + template + void _IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); + + template + void _IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); + // for Subpopulation::ExecuteMethod_takeMigrants() friend Subpopulation; }; @@ -428,6 +437,14 @@ class Individual_Class : public EidosDictionaryUnretained_Class EidosValue_SP ExecuteMethod_outputIndividualsToVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_readIndividualsFromVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_setSpatialPosition(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + + EidosValue_SP ExecuteMethod_demandPhenotype(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + + // phenotype demand for all traits for a vector of target individuals, across all chromosomes + // if f_force_recalc is true all values are recalculated; if false, only NAN trait values are recalculated + // see also the methods class _IncorporateEffects_X() methods in class Individual, called by this method + template + void DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; }; diff --git a/core/interaction_type.cpp b/core/interaction_type.cpp index c80d0d67..bdd0f30e 100755 --- a/core/interaction_type.cpp +++ b/core/interaction_type.cpp @@ -514,7 +514,7 @@ void InteractionType::EvaluateSubpopulation(Subpopulation *p_subpop) // Note that interaction() callbacks are non-species-specific, so we fetch from the Community with species nullptr. // Callbacks used depend upon the exerter subpopulation, so this is snapping the callbacks for subpop as exerters; // the subpopulation of receivers does not influence the choice of which callbacks are used. - subpop_data->evaluation_interaction_callbacks_ = community_.ScriptBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosInteractionCallback, -1, interaction_type_id_, subpop_id, -1, nullptr); + subpop_data->evaluation_interaction_callbacks_ = community_.ScriptBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosInteractionCallback, -1, interaction_type_id_, subpop_id, -1, -1, nullptr); // Note that we do not create the k-d tree here. Non-spatial models will never have a k-d tree; spatial models may or // may not need one, depending upon what methods are called by the client, which may vary cycle by cycle. diff --git a/core/mutation.cpp b/core/mutation.cpp index 0e2872bc..3d68c2b6 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -87,7 +87,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! if (traitType == TraitType::kMultiplicative) { @@ -207,7 +207,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ is_neutral_ = false; species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! if (traitType == TraitType::kMultiplicative) { @@ -292,7 +292,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! if (traitType == TraitType::kMultiplicative) { @@ -357,7 +357,7 @@ void Mutation::SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, s species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags } // cache values used by the fitness calculation code for speed; see header @@ -390,7 +390,7 @@ void Mutation::SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, s // effect has changed to neutral; other trait effects might be non-neutral, which we don't check Species &species = mutation_type_ptr_->species_; - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags // cache values used by the fitness calculation code for speed; see header // for a neutral trait, we can set up this info very quickly diff --git a/core/population.cpp b/core/population.cpp index 8cd4e241..ddd1a823 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5201,8 +5201,8 @@ void Population::RecalculateFitness(slim_tick_t p_tick) // calculate the fitnesses of the parents and make lookup tables; the main thing we do here is manage the mutationEffect() callbacks // as per the SLiM design spec, we get the list of callbacks once, and use that list throughout this stage, but we construct // subsets of it for each subpopulation, so that UpdateFitness() can just use the callback list as given to it - std::vector mutationEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1); - std::vector fitnessEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, -1, -1); + std::vector mutationEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1); + std::vector fitnessEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, -1, -1, -1); bool no_active_callbacks = true; for (SLiMEidosBlock *callback : mutationEffect_callbacks) @@ -5393,7 +5393,7 @@ void Population::RecalculateFitness(slim_tick_t p_tick) { slim_objectid_t subpop_id = subpop_pair.first; Subpopulation *subpop = subpop_pair.second; - std::vector subpop_mutationEffect_callbacks; + std::vector subpop_mutationEffect_callbacks; // FIXME MULTITRAIT won't need this any more std::vector subpop_fitnessEffect_callbacks; // Get mutationEffect() and fitnessEffect() callbacks that apply to this subpopulation diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index c7d25c90..555cb451 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -1493,10 +1493,14 @@ void SLiMEidosBlock::PrintDeclaration(std::ostream& p_out, Community *p_communit case SLiMEidosBlockType::SLiMEidosMutationEffectCallback: { - // mutationEffect( [, ]) + // mutationEffect( [, [, ]]) p_out << "mutationEffect(m" << mutation_type_id_; if (subpopulation_id_ != -1) p_out << ", p" << subpopulation_id_; + else if (trait_index_ != -1) + p_out << ", NULL"; + if (trait_index_ != -1) + p_out << ", " << trait_index_ << ""; p_out << ")"; break; } @@ -1540,7 +1544,7 @@ void SLiMEidosBlock::PrintDeclaration(std::ostream& p_out, Community *p_communit else if (chromosome_id_ != -1) p_out << "NULL"; if (chromosome_id_ != -1) - p_out << ", \"" << chromosome_id_ << "\""; + p_out << ", " << chromosome_id_ << ""; p_out << ")"; break; } diff --git a/core/slim_eidos_block.h b/core/slim_eidos_block.h index 69fd2eba..6b9be7be 100644 --- a/core/slim_eidos_block.h +++ b/core/slim_eidos_block.h @@ -156,6 +156,7 @@ class SLiMEidosBlock : public EidosDictionaryUnretained Species *ticks_spec_ = nullptr; // NOT OWNED: the species to which the block is synchronized (only active when that species is active) slim_objectid_t mutation_type_id_ = -1; // -1 if not limited by this slim_objectid_t subpopulation_id_ = -1; // -1 if not limited by this + slim_objectid_t trait_index_ = -1; // -1 if not limited by this slim_objectid_t interaction_type_id_ = -1; // -1 if not limited by this IndividualSex sex_specificity_ = IndividualSex::kUnspecified; // IndividualSex::kUnspecified if not limited by this int64_t chromosome_id_ = -1; // -1 if not limited by this diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index ab84c329..44ba2c8a 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1362,6 +1362,7 @@ const std::string &gStr_containsMarkerMutation = EidosRegisteredString("contains const std::string &gStr_haplosomesForChromosomes = EidosRegisteredString("haplosomesForChromosomes", gID_haplosomesForChromosomes); const std::string &gStr_offsetForTrait = EidosRegisteredString("offsetForTrait", gID_offsetForTrait); const std::string &gStr_phenotypeForTrait = EidosRegisteredString("phenotypeForTrait", gID_phenotypeForTrait); +const std::string &gStr_demandPhenotype = EidosRegisteredString("demandPhenotype", gID_demandPhenotype); const std::string &gStr_setOffsetForTrait = EidosRegisteredString("setOffsetForTrait", gID_setOffsetForTrait); const std::string &gStr_setPhenotypeForTrait = EidosRegisteredString("setPhenotypeForTrait", gID_setPhenotypeForTrait); const std::string &gStr_relatedness = EidosRegisteredString("relatedness", gID_relatedness); diff --git a/core/slim_globals.h b/core/slim_globals.h index 947c7f26..daacd70d 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -950,6 +950,7 @@ extern const std::string &gStr_containsMarkerMutation; extern const std::string &gStr_haplosomesForChromosomes; extern const std::string &gStr_offsetForTrait; extern const std::string &gStr_phenotypeForTrait; +extern const std::string &gStr_demandPhenotype; extern const std::string &gStr_setOffsetForTrait; extern const std::string &gStr_setPhenotypeForTrait; extern const std::string &gStr_relatedness; @@ -1434,6 +1435,7 @@ enum _SLiMGlobalStringID : int { gID_haplosomesForChromosomes, gID_offsetForTrait, gID_phenotypeForTrait, + gID_demandPhenotype, gID_setOffsetForTrait, gID_setPhenotypeForTrait, gID_relatedness, diff --git a/core/species.cpp b/core/species.cpp index cb778005..55c75815 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -2522,8 +2522,8 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid { slim_objectid_t subpop_id = subpop_pair.first; Subpopulation *subpop = subpop_pair.second; - std::vector mutationEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, subpop_id, -1); - std::vector fitnessEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, subpop_id, -1); + std::vector mutationEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, subpop_id, -1, -1); + std::vector fitnessEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, subpop_id, -1, -1); subpop->UpdateFitness(mutationEffect_callbacks, fitnessEffect_callbacks); } @@ -2579,11 +2579,11 @@ Subpopulation *Species::SubpopulationWithName(const std::string &p_subpop_name) #pragma mark Running cycles #pragma mark - -std::vector Species::CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, int64_t p_chromosome_id) +std::vector Species::CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_objectid_t p_trait_index, int64_t p_chromosome_id) { // Callbacks are species-specific; this method calls up to the community, which manages script blocks, // but does a species-specific search. - return community_.ScriptBlocksMatching(p_tick, p_event_type, p_mutation_type_id, p_interaction_type_id, p_subpopulation_id, p_chromosome_id, this); + return community_.ScriptBlocksMatching(p_tick, p_event_type, p_mutation_type_id, p_interaction_type_id, p_subpopulation_id, p_trait_index, p_chromosome_id, this); } void Species::RunInitializeCallbacks(void) @@ -2610,7 +2610,7 @@ void Species::RunInitializeCallbacks(void) has_implicit_chromosome_ = false; // execute initialize() callbacks, which should always have a tick of 0 set - std::vector init_blocks = CallbackBlocksMatching(0, SLiMEidosBlockType::SLiMEidosInitializeCallback, -1, -1, -1, -1); + std::vector init_blocks = CallbackBlocksMatching(0, SLiMEidosBlockType::SLiMEidosInitializeCallback, -1, -1, -1, -1, -1); for (auto script_block : init_blocks) community_.ExecuteEidosEvent(script_block); @@ -3009,10 +3009,10 @@ void Species::EmptyGraveyard(void) void Species::WF_GenerateOffspring(void) { slim_tick_t tick = community_.Tick(); - std::vector mate_choice_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMateChoiceCallback, -1, -1, -1, -1); - std::vector modify_child_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosModifyChildCallback, -1, -1, -1, -1); - std::vector recombination_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, -1, -1); - std::vector mutation_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMutationCallback, -1, -1, -1, -1); + std::vector mate_choice_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMateChoiceCallback, -1, -1, -1, -1, -1); + std::vector modify_child_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosModifyChildCallback, -1, -1, -1, -1, -1); + std::vector recombination_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, -1, -1, -1); + std::vector mutation_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMutationCallback, -1, -1, -1, -1, -1); bool mate_choice_callbacks_present = mate_choice_callbacks.size(); bool modify_child_callbacks_present = modify_child_callbacks.size(); bool recombination_callbacks_present = recombination_callbacks.size(); @@ -3151,10 +3151,10 @@ void Species::WF_SwapGenerations(void) void Species::nonWF_GenerateOffspring(void) { slim_tick_t tick = community_.Tick(); - std::vector reproduction_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosReproductionCallback, -1, -1, -1, -1); - std::vector modify_child_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosModifyChildCallback, -1, -1, -1, -1); - std::vector recombination_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, -1, -1); - std::vector mutation_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMutationCallback, -1, -1, -1, -1); + std::vector reproduction_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosReproductionCallback, -1, -1, -1, -1, -1); + std::vector modify_child_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosModifyChildCallback, -1, -1, -1, -1, -1); + std::vector recombination_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, -1, -1, -1); + std::vector mutation_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMutationCallback, -1, -1, -1, -1, -1); // choose templated variants for GenerateIndividualsX() methods of Subpopulation, called during reproduction() callbacks // this is an optimization technique that lets us optimize away unused cruft at compile time @@ -3592,7 +3592,7 @@ void Species::nonWF_MergeOffspring(void) void Species::nonWF_ViabilitySurvival(void) { slim_tick_t tick = community_.Tick(); - std::vector survival_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosSurvivalCallback, -1, -1, -1, -1); + std::vector survival_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosSurvivalCallback, -1, -1, -1, -1, -1); bool survival_callbacks_present = survival_callbacks.size(); bool no_active_callbacks = true; diff --git a/core/species.h b/core/species.h index 004f9109..d7bbbb4a 100644 --- a/core/species.h +++ b/core/species.h @@ -469,7 +469,7 @@ class Species : public EidosDictionaryUnretained void DeleteAllMutationRuns(void); // for cleanup // Running cycles - std::vector CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, int64_t p_chromosome_id); + std::vector CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_objectid_t p_trait_index, int64_t p_chromosome_id); void RunInitializeCallbacks(void); void CreateAndPromulgateMutationBlock(void); void EndCurrentChromosome(bool starting_new_chromosome); diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 62edacf9..179e232f 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1644,15 +1644,27 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires type to be either 'multiplicative' (or 'mul'), or 'additive' ('add')." << EidosTerminate(); // baselineOffset - double baselineOffset; + slim_effect_t baselineOffset; if (baselineOffset_value->Type() == EidosValueType::kValueNULL) - baselineOffset = (type == TraitType::kMultiplicative) ? 1.0 : 0.0; + { + baselineOffset = (slim_effect_t)((type == TraitType::kMultiplicative) ? 1.0 : 0.0); + } else - baselineOffset = baselineOffset_value->FloatAtIndex_NOCAST(0, nullptr); + { + double baselineOffset_double = baselineOffset_value->FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(baselineOffset_double)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires baselineOffset to be a finite value (not NAN or INF)." << EidosTerminate(); + + baselineOffset = (slim_effect_t)baselineOffset_double; // this can round to infinity + + if (!std::isfinite(baselineOffset)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires baselineOffset to be representable as a finite single-precision floating-point number; the value given rounded to infinity." << EidosTerminate(); + } - if (!std::isfinite(baselineOffset)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires baselineOffset to be a finite value (not NAN or INF)." << EidosTerminate(); + if ((type == TraitType::kMultiplicative) && (baselineOffset < 0.0)) + baselineOffset = 0.0; // check that the default distribution is used or not used, in its entirety if (individualOffsetMean_value->Type() != individualOffsetSD_value->Type()) @@ -4158,6 +4170,7 @@ EidosValue_SP Species::ExecuteMethod_registerMutationEffectCallback(EidosGlobalS new_script_block->mutation_type_id_ = mut_type_id; new_script_block->subpopulation_id_ = subpop_id; + new_script_block->trait_index_ = -1; // FIXME MULTITRAIT: should provide the ability to set a trait index // SPECIES CONSISTENCY CHECK (done by AddScriptBlock()) community_.AddScriptBlock(new_script_block, &p_interpreter, nullptr); // takes ownership from us diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 3f98829b..413e147e 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1131,6 +1131,9 @@ Subpopulation::Subpopulation(Population &p_population, slim_objectid_t p_subpopu , gui_premigration_size_(p_subpop_size) #endif { + // resize our internal per-trait state up to the number of traits we're modeling + per_trait_subpop_caches_.resize(species_.TraitCount()); + // if the species knows that its chromosomes involve null haplosomes, then we inherit that knowledge has_null_haplosomes_ = species_.ChromosomesUseNullHaplosomes(); @@ -1179,6 +1182,9 @@ Subpopulation::Subpopulation(Population &p_population, slim_objectid_t p_subpopu , gui_premigration_size_(p_subpop_size) #endif { + // resize our internal per-trait state up to the number of traits we're modeling + per_trait_subpop_caches_.resize(species_.TraitCount()); + // if the species knows that its chromosomes involve null haplosomes, then we inherit that knowledge has_null_haplosomes_ = species_.ChromosomesUseNullHaplosomes(); @@ -2438,6 +2444,7 @@ void Subpopulation::UpdateWFFitnessBuffers(bool p_pure_neutral) } } +// FIXME MULTITRAIT: should return slim_effect_t so the caller doesn't have to cast double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, double p_computed_fitness, std::vector &p_mutationEffect_callbacks, Individual *p_individual) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::ApplyMutationEffectCallbacks(): running Eidos callback"); @@ -2570,6 +2577,7 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int // p_homozygous == -1 means the mutation faces a null haplosome; otherwise, 0 means heterozyg., 1 means homozyg. // that gets translated into Eidos values of NULL, F, and T, respectively + // FIXME MULTITRAIT: should the semantics here be changed? haploid mutations should maybe be 1, and -1 should be hemizygous specifically? if (mutationEffect_callback->contains_homozygous_) { if (p_homozygous == -1) diff --git a/core/subpopulation.h b/core/subpopulation.h index acbb6677..1ce64abb 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -160,6 +160,18 @@ class Subpopulation : public EidosDictionaryUnretained std::vector registered_mutation_callbacks_; // NOT OWNED: valid only during EvolveSubpopulation; callbacks used when this subpop is parental std::vector registered_reproduction_callbacks_; // nonWF only; NOT OWNED: valid only during EvolveSubpopulation; callbacks used when this subpop is parental + // These per-subpopulation caches are used by IndividualClass::DemandPhenotype() and are valid only within + // that method. There is a std::vector of PerTraitSubpopCache structs with one entry per trait in the + // species. When not in use, that vector should still have one entry per trait, with empty/nullptr values. + typedef struct _PerTraitSubpopCaches { + std::vector mutationEffect_callbacks_per_trait; // NOT OWNED: mutationEffect() callbacks per subpopulation per trait + void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Hemizygous_TEMPLATED)(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Diploid_TEMPLATED)(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + } PerTraitSubpopCaches; + + std::vector per_trait_subpop_caches_; // one entry per trait, indexed by trait index + // WF only: // Fitness caching. Every individual now caches its fitness internally, and that is what is used by SLiMgui and by the cachedFitness() method of Subpopulation. // These fitness cache buffers are additional to that, used only in WF models. They are used for two things. First, as the data source for setting up our lookup diff --git a/core/trait.cpp b/core/trait.cpp index 37d9eecb..472c9b01 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -11,11 +11,25 @@ #include "species.h" -Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, double p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect) : - index_(-1), name_(p_name), type_(p_type), baselineOffset_(p_baselineOffset), +Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, slim_effect_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect) : + index_(-1), name_(p_name), type_(p_type), individualOffsetMean_(p_individualOffsetMean), individualOffsetSD_(p_individualOffsetSD), directFitnessEffect_(p_directFitnessEffect), community_(p_species.community_), species_(p_species) { + // offsets must always be finite + if (!std::isfinite(p_baselineOffset)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property baselineOffset requires a finite value (not NAN or INF)." << EidosTerminate(); + if (!std::isfinite(individualOffsetMean_)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property individualOffsetMean requires a finite value (not NAN or INF)." << EidosTerminate(); + if (!std::isfinite(individualOffsetSD_)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property individualOffsetSD requires a finite value (not NAN or INF)." << EidosTerminate(); + + // effects for multiplicative traits clip at 0.0 + if ((type_ == TraitType::kMultiplicative) && (p_baselineOffset < 0.0)) + baselineOffset_ = 0.0; + else + baselineOffset_ = p_baselineOffset; + _RecacheIndividualOffsetDistribution(); } @@ -25,7 +39,14 @@ void Trait::_RecacheIndividualOffsetDistribution(void) if (individualOffsetSD_ == 0.0) { individualOffsetFixed_ = true; - individualOffsetFixedValue_ = static_cast(individualOffsetMean_); + + // effects for multiplicative traits clip at 0.0 + slim_effect_t offset = static_cast(individualOffsetMean_); + + if ((type_ == TraitType::kMultiplicative) && (offset < 0.0)) + individualOffsetFixedValue_ = 0.0; + else + individualOffsetFixedValue_ = offset; } else { @@ -54,7 +75,13 @@ slim_effect_t Trait::_DrawIndividualOffset(void) const // note the individualOffsetSD_ == 0 case was already handled by DrawIndividualOffset() gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - return static_cast(gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_); + slim_effect_t offset = static_cast(gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_); + + // effects for multiplicative traits clip at 0.0 + if ((type_ == TraitType::kMultiplicative) && (offset < 0.0)) + offset = 0.0; + + return offset; } EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) @@ -80,6 +107,8 @@ EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) static EidosValue_SP static_type_string_multiplicative; static EidosValue_SP static_type_string_additive; + // FIXME PARALLEL static string allocation like this should be done at startup, before we go multithreaded; this should not need a critical section + // search for "static EidosValue_SP" and fix all of them #pragma omp critical (GetProperty_trait_type) { if (!static_type_string_multiplicative) @@ -142,7 +171,12 @@ void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_v if (!std::isfinite(value)) EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property baselineOffset requires a finite value (not NAN or INF)." << EidosTerminate(); - baselineOffset_ = value; + // effects for multiplicative traits clip at 0.0 + if ((type_ == TraitType::kMultiplicative) && (value < 0.0)) + baselineOffset_ = 0.0; + else + baselineOffset_ = (slim_effect_t)value; + return; } case gID_directFitnessEffect: diff --git a/core/trait.h b/core/trait.h index 6b37b0d5..08ecb85e 100644 --- a/core/trait.h +++ b/core/trait.h @@ -58,7 +58,7 @@ class Trait : public EidosDictionaryRetained TraitType type_; // multiplicative or additive // offsets - double baselineOffset_; + slim_effect_t baselineOffset_; bool individualOffsetFixed_; // true if individualOffsetSD_ == 0.0 slim_effect_t individualOffsetFixedValue_; // equal to individualOffsetMean_ if individualOffsetFixed_ == true; pre-cast for speed @@ -81,7 +81,7 @@ class Trait : public EidosDictionaryRetained Trait& operator=(const Trait&) = delete; // no copying Trait(void) = delete; // no null constructor - explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, double p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); + explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, slim_effect_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); ~Trait(void); inline __attribute__((always_inline)) int64_t Index(void) const { return index_; } @@ -89,6 +89,8 @@ class Trait : public EidosDictionaryRetained inline __attribute__((always_inline)) TraitType Type(void) const { return type_; } inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } + slim_effect_t BaselineOffset(void) const { return baselineOffset_; }; + void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetFixed_ and individualOffsetFixedValue_ slim_effect_t _DrawIndividualOffset(void) const; // draws from a normal distribution defined by individualOffsetMean_ and individualOffsetSD_ inline slim_effect_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } From 90beaedb39a766532e3b62d677abe23206071b0b Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 25 Dec 2025 13:25:11 -0600 Subject: [PATCH 040/107] fix failing unit test --- core/slim_test_genetics.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 8d07b732..11cfe542 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1092,9 +1092,9 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.height = sim.traitsWithNames('height'); }", "new value for read-only property", __LINE__); SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.weight = sim.traitsWithNames('weight'); }", "new value for read-only property", __LINE__); - // individual trait property access (not yet fully implemented) - SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.height, rep(0.0, 5))) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.weight, rep(0.0, 5))) stop(); }"); + // individual trait property access + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.height, rep(NAN, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.weight, rep(NAN, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0; if (!identical(p1.individuals.height, rep(10.0, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 10.0; if (!identical(p1.individuals.weight, rep(10.0, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0:14; if (!identical(p1.individuals.height, 10.0:14)) stop(); }"); From a0df7647e01da234f8d15e8d13c628f7345afc01 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 25 Dec 2025 13:26:27 -0600 Subject: [PATCH 041/107] change initializeMutationType[Nuc]() to have [Ns$ distributionType = NULL] --- QtSLiM/help/SLiMHelpFunctions.html | 8 ++++---- SLiMgui/SLiMHelpFunctions.rtf | 26 +++++++++++++++++--------- VERSIONS | 1 + core/community_eidos.cpp | 4 ++-- core/species_eidos.cpp | 26 ++++++++++++++++++++++---- eidos/eidos_call_signature.cpp | 16 ++++++++++++++-- 6 files changed, 60 insertions(+), 21 deletions(-) diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 25122212..47c9d932 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -103,12 +103,12 @@

Set a mutation rate map from data read from the file at path.  This function is essentially a wrapper for initializeMutationRate() that uses readCSV() and passes the data through.  The file is expected to contain two columns of data.  The first column must be integer start positions for rate map regions; the first region should start at position 0 if the map’s positions are 0-based, or at position 1 if the map’s positions are 1-based; in the latter case, 1 will be subtracted from every position since SLiM uses 0-based positions.  The second column must be float rates, relative to the scaling factor specified in scale; for example, if a given rate is 1.2 and scale is 1e-8 (the default), the rate used will be 1.2e-8.  No column header line should be present; the file should start immediately with numerical data.  The expected separator between columns is a tab character by default, but may be passed in sep; the expected decimal separator is a period by default, but may be passed in dec.  Once read, the map is converted into a rate map specified with end positions, rather than start positions, and the position given by lastPosition is used as the end of the last rate region; it should be the last position of the chromosome.

See readCSV() for further details on sep and dec, which are passed through to it; and see initializeMutationRate() for details on how the rate map is validated and used, and how the sex parameter is used.

This function is written in Eidos, and its source code can be viewed with functionSource(), so you can copy and modify its code if you need to modify its functionality.

-

(object<MutationType>$)initializeMutationType(is$ id, numeric$ dominanceCoeff, string$ distributionType, ...)

+

(object<MutationType>$)initializeMutationType(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

Add a mutation type at initialization time.  The id must not already be used for any mutation type in the simulation.  The id parameter may be either an integer giving the ID of the new mutation type, or a string giving the name of the new mutation type (such as "m5" to specify an ID of 5).  The global symbol for the new mutation type, such as m5, is immediately available; the return value also provides the new object.

-

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but can subsequently be configured with the setDefaultHemizygousDominanceForTrait() method if desired.

-

The distributionType and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits.  The DES for the mutation type for a specific trait can subsequently be separately configured with the setEffectDistributionForTrait() method if desired.  The distributionType parameter may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for an exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  See the MutationType class documentation for discussion of the various DESs and their uses.

+

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but the setDefaultHemizygousDominanceForTrait() method can configure it subsequently if desired.

+

The distributionType and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits.  The DES for the mutation type for a specific trait can subsequently be separately configured with the setEffectDistributionForTrait() method if desired.  The distributionType parameter may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for an exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  If distributionType is NULL (the default), a fixed effect of 0.0 is used, representing a neutral DES; this is equivalent to type "f" except that the value 0.0 is assumed and must not be supplied.  See the MutationType class documentation for discussion of the various DESs and their uses.

Note that by default in WF models, all mutations of a given mutation type will be converted into Substitution objects when they reach fixation, for efficiency reasons.  If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the convertToSubstitution property of MutationType to F.  In contrast, by default in nonWF models mutations will not be converted into Substitution objects when they reach fixation; convertToSubstitution is F by default in nonWF models.  To enable conversion in nonWF models for neutral mutation types with no indirect fitness effects, you should therefore set convertToSubstitution to T.

-

(object<MutationType>$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, string$ distributionType, ...)

+

(object<MutationType>$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

Add a nucleotide-based mutation type at initialization time.  This function is identical to initializeMutationType() except that the new mutation type will be nucleotide-based – in other words, mutations belonging to the new mutation type will have an associated nucleotide.  This function may be called only in nucleotide-based models (as enabled by the nucleotideBased parameter to initializeSLiMOptions()).

Nucleotide-based mutations always use a mutationStackGroup of -1 and a mutationStackPolicy of "l".  This ensures that a new nucleotide mutation always replaces any previously existing nucleotide mutation at a given position, regardless of the mutation types of the nucleotide mutations.  These values are set automatically by initializeMutationTypeNuc(), and may not be changed.

See the documentation for initializeMutationType() for all other discussion.

diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index bf5cf3b9..21979187 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -870,7 +870,7 @@ This function is written in Eidos, and its source code can be viewed with \f2\fs20 , so you can copy and modify its code if you need to modify its functionality.\ \pard\pardeftab543\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf0 (object$)initializeMutationType(is$\'a0id, numeric$\'a0dominanceCoeff, string$\'a0distributionType, ...) +\f1\fs18 \cf0 (object$)initializeMutationType(is$\'a0id, numeric$\'a0dominanceCoeff, \cf2 [Ns$\'a0distributionType\'a0=\'a0NULL]\cf0 , ...) \f4 \ \pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 @@ -899,9 +899,9 @@ The \f1\fs18 setDefaultDominanceForTrait() \f2\fs20 method if desired. Note that the mutation type\'92s default hemizygous dominance coefficient is not supplied to this function; it always defaults to \f1\fs18 1.0 -\f2\fs20 , but can subsequently be configured with the +\f2\fs20 , but the \f1\fs18 setDefaultHemizygousDominanceForTrait() -\f2\fs20 method if desired.\ +\f2\fs20 method can configure it subsequently if desired.\ The \f1\fs18 distributionType \f2\fs20 and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits. The DES for the mutation type for a specific trait can subsequently be separately configured with the @@ -948,11 +948,20 @@ The \f1\fs18 "s" \f2\fs20 , in which case the ellipsis should supply a \f1\fs18 string$ -\f2\fs20 Eidos script parameter. See the +\f2\fs20 Eidos script parameter. If +\f1\fs18 distributionType +\f2\fs20 is +\f1\fs18 NULL +\f2\fs20 (the default), a fixed effect of +\f1\fs18 0.0 +\f2\fs20 is used, representing a neutral DES; this is equivalent to type +\f1\fs18 "f" +\f2\fs20 except that the value +\f1\fs18 0.0 +\f2\fs20 is assumed and must not be supplied. See the \f1\fs18 MutationType \f2\fs20 class documentation for discussion of the various DESs and their uses.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \expnd0\expndtw0\kerning0 +\expnd0\expndtw0\kerning0 Note that by default in WF models, all mutations of a given mutation type will be converted into \f1\fs18 Substitution \f2\fs20 objects when they reach fixation, for efficiency reasons. If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the @@ -974,7 +983,8 @@ Note that by default in WF models, all mutations of a given mutation type will b \f2\fs20 .\ \pard\pardeftab543\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf2 (object$)initializeMutationTypeNuc(is$\'a0id, numeric$\'a0dominanceCoeff, string$\'a0distributionType, ...)\ +\f1\fs18 \cf2 (object$)initializeMutationTypeNuc(is$\'a0id, numeric$\'a0dominanceCoeff, \kerning1\expnd0\expndtw0 [Ns$\'a0distributionType\'a0=\'a0NULL]\expnd0\expndtw0\kerning0 +, ...)\ \pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 \f2\fs20 \cf2 Add a nucleotide-based mutation type at initialization time. This function is identical to @@ -1332,7 +1342,6 @@ If \f1\fs18 initializeChromosome() \f2\fs20 , allowing a different mutation run count to be specified for each chromosome in multi-chromosome models.\expnd0\expndtw0\kerning0 \ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 \kerning1\expnd0\expndtw0 If \f1\fs18 preventIncidentalSelfing \f2\fs20 is @@ -1412,7 +1421,6 @@ If \f2\fs20 for \f1\fs18 checkInfiniteLoops \f2\fs20 to disable these checks. There is no way to turn these checks on or off for individual loops; it is a global setting.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 This function will likely be extended with further options in the future, added on to the end of the argument list. Using named arguments with this call is recommended for readability. Note that turning on optional features may increase the runtime and memory footprint of SLiM.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 diff --git a/VERSIONS b/VERSIONS index 3ee65afc..8cd94955 100644 --- a/VERSIONS +++ b/VERSIONS @@ -109,6 +109,7 @@ multitrait branch: allow the destination subpop to be given as a source, iff all rates supplied are 0.0, to stop all migration: allSubpops.setMigrationRates(allSubpops, 0.0) fix #567, plot windows can have their aspect ratio distorted due to screen size and other constraints add Individual class method +(void)demandPhenotype([Nio trait = NULL], [l$ forceRecalc = F]) + extend initializeMutationType() and initializeMutationTypeNuc() to make the effect distribution optional with [Ns$ distributionType = NULL], where NULL is equivalent to `"f", 0.0` version 5.1 (Eidos version 4.1): diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index 30cab918..269a5035 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -120,9 +120,9 @@ const std::vector *Community::ZeroTickFunctionSignat sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeInteractionType, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_InteractionType_Class, "SLiM")) ->AddIntString_S("id")->AddString_S(gStr_spatiality)->AddLogical_OS(gStr_reciprocal, gStaticEidosValue_LogicalF)->AddNumeric_OS(gStr_maxDistance, gStaticEidosValue_FloatINF)->AddString_OS(gStr_sexSegregation, gStaticEidosValue_StringDoubleAsterisk)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeMutationType, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class, "SLiM")) - ->AddIntString_S("id")->AddNumeric_S("dominanceCoeff")->AddString_S("distributionType")->AddEllipsis()); + ->AddIntString_S("id")->AddNumeric_S("dominanceCoeff")->AddString_OSN("distributionType", gStaticEidosValueNULL)->AddEllipsis()); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeMutationTypeNuc, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class, "SLiM")) - ->AddIntString_S("id")->AddNumeric_S("dominanceCoeff")->AddString_S("distributionType")->AddEllipsis()); + ->AddIntString_S("id")->AddNumeric_S("dominanceCoeff")->AddString_OSN("distributionType", gStaticEidosValueNULL)->AddEllipsis()); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeRecombinationRate, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric("rates")->AddInt_ON("ends", gStaticEidosValueNULL)->AddString_OS("sex", gStaticEidosValue_StringAsterisk)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeTrait, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class, "SLiM"))->AddString_S("name")->AddString_S("type")->AddFloat_OSN("baselineOffset", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetMean", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetSD", gStaticEidosValueNULL)->AddLogical_OS("directFitnessEffect", gStaticEidosValue_LogicalF)); diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 179e232f..efdcc22e 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -584,8 +584,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const return symbol_entry.second; } -// ********************* (object$)initializeMutationType(is$ id, numeric$ dominanceCoeff, string$ distributionType, ...) -// ********************* (object$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, string$ distributionType, ...) +// ********************* (object$)initializeMutationType(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...) +// ********************* (object$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...) // EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -606,9 +606,15 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: EidosValue *distributionType_value = p_arguments[2].get(); std::ostream &output_stream = p_interpreter.ExecutionOutputStream(); + // BCH 12/25/2025: We now allow NULL for distributionType, which is shorthand for `"f", 0.0` (neutral) + bool defaultDistribution = false; + + if (distributionType_value->Type() == EidosValueType::kValueNULL) + defaultDistribution = true; + slim_objectid_t map_identifier = SLiM_ExtractObjectIDFromEidosValue_is(id_value, 0, 'm'); double dominance_coeff = dominanceCoeff_value->NumericAtIndex_NOCAST(0, nullptr); - std::string DES_type_string = distributionType_value->StringAtIndex_NOCAST(0, nullptr); + std::string DES_type_string = (defaultDistribution ? "f" : distributionType_value->StringAtIndex_NOCAST(0, nullptr)); if (community_.MutationTypeWithID(map_identifier)) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeMutationType): " << p_function_name << "() mutation type m" << map_identifier << " already defined." << EidosTerminate(); @@ -618,7 +624,19 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: std::vector DES_parameters; std::vector DES_strings; - MutationType::ParseDESParameters(DES_type_string, p_arguments.data() + 3, (int)p_arguments.size() - 3, &DES_type, &DES_parameters, &DES_strings); + if (defaultDistribution) + { + // The default distribution is a fixed effect of 0.0, and ellipsis arguments may not be supplied + if (p_arguments.size() != 3) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeMutationType): with distributionType of NULL, ellipsis arguments may not be supplied to " << p_function_name << "(); the distribution of effect sizes is already completely specified." << EidosTerminate(); + + DES_type = DESType::kFixed; + DES_parameters.push_back(0.0); + } + else + { + MutationType::ParseDESParameters(DES_type_string, p_arguments.data() + 3, (int)p_arguments.size() - 3, &DES_type, &DES_parameters, &DES_strings); + } #ifdef SLIMGUI // each new mutation type gets a unique zero-based index, used by SLiMgui to categorize mutations diff --git a/eidos/eidos_call_signature.cpp b/eidos/eidos_call_signature.cpp index 568ce287..0121e6d3 100644 --- a/eidos/eidos_call_signature.cpp +++ b/eidos/eidos_call_signature.cpp @@ -169,8 +169,20 @@ EidosCallSignature *EidosCallSignature::AddArgWithDefault(EidosValueMask p_arg_m EidosCallSignature *EidosCallSignature::AddEllipsis(void) { - if (has_optional_args_) - EIDOS_TERMINATION << "ERROR (EidosCallSignature::AddEllipsis): cannot add an ellipsis after an optional argument has been added." << EidosTerminate(nullptr); + // BCH 12/25/2025: I'm removing this check now, and relaxing this requirement. This is to allow the new + // signature of initializeMutationType(), for which I want `[Ns$ distributionType = NULL], ...`. The + // new rule will be: an ellipsis is allowed after an optional argument, but in that case, any argument + // in that position will be applied to the optional argument first. If you want to supply ellipsis + // arguments, the optional arguments before it effectively become non-optional. The alternative would + // be to subsume the distributionType argument into the ellipsis section of the signature and let the + // implementation of initializeMutationType() figure out the situation itself, which would not require + // this rule change in Eidos; but that would make the function signature much more opaque. This change + // is a little weird, but I doubt anybody will think about argument processing hard enough to realize + // what has been done. No change was needed to the argument processing code to implement this; the code + // already behaves this way since it processes optional arguments with a greedy algorithm. + // + //if (has_optional_args_) + // EIDOS_TERMINATION << "ERROR (EidosCallSignature::AddEllipsis): cannot add an ellipsis after an optional argument has been added." << EidosTerminate(nullptr); if (has_ellipsis_) EIDOS_TERMINATION << "ERROR (EidosCallSignature::AddEllipsis): cannot add more than one ellipsis." << EidosTerminate(nullptr); From 34271170ce2bbbddb38054e10854bfdcbfc3215d Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 25 Dec 2025 13:37:41 -0600 Subject: [PATCH 042/107] fix verbose output for initializeMutationType() --- core/species_eidos.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index efdcc22e..6756dfae 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -669,17 +669,26 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: } else { - output_stream << p_function_name << "(" << map_identifier << ", " << dominance_coeff << ", \"" << DES_type << "\""; + output_stream << p_function_name << "(" << map_identifier << ", " << dominance_coeff; - if (DES_parameters.size() > 0) + if (defaultDistribution) { - for (double DES_param : DES_parameters) - output_stream << ", " << DES_param; + output_stream << ", NULL"; } else { - for (const std::string &DES_param : DES_strings) - output_stream << ", \"" << DES_param << "\""; + output_stream << ", \"" << DES_type << "\""; + + if (DES_parameters.size() > 0) + { + for (double DES_param : DES_parameters) + output_stream << ", " << DES_param; + } + else + { + for (const std::string &DES_param : DES_strings) + output_stream << ", \"" << DES_param << "\""; + } } output_stream << ");" << std::endl; From b890c8853e101db43741e64ab620256f0e02af59 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 25 Dec 2025 14:58:00 -0600 Subject: [PATCH 043/107] clean up multiplicative trait effect clamping --- QtSLiM/help/SLiMHelpClasses.html | 3 + QtSLiM/help/SLiMHelpFunctions.html | 4 +- SLiMgui/SLiMHelpClasses.rtf | 18 ++++ SLiMgui/SLiMHelpFunctions.rtf | 8 +- VERSIONS | 1 + core/individual.cpp | 142 ++++++++++++++++++++++------- 6 files changed, 139 insertions(+), 37 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index e617674f..81891509 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -504,6 +504,7 @@

+ (void)setOffsetForTrait([Nio<Trait> trait = NULL], [Nif offset = NULL])

Sets the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter offset must follow one of four patterns.  In the first pattern, offset is NULL; this draws the offset for each of the specified traits from each trait’s individual-offset distribution (defined by each trait’s individualOffsetMean and individualOffsetSD properties) in each target individual.  (Note that individual offsets are automatically drawn from these distributions when an individual is created; this re-draws new offset values.)  In the second pattern, offset is a singleton value; this sets the given offset for each of the specified traits in each target individual.  In the third pattern, offset is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual.  In the fourth pattern, offset is of length equal to the number of specified traits times the number of target individuals; this uses offset to provide a different offset value for each trait in each individual, using consecutive values from offset to set the offset for each of the specified traits in one individual before moving to the next individual.

+

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  This policy applies to individual offsets, so an offset value passed in or generated here may not be the value actually used by SLiM or subsequently returned by offsetForTrait().

+ (void)setPhenotypeForTrait(Nio<Trait> trait, numeric phenotype)

Sets the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter phenotype must follow one of three patterns.  In the first pattern, phenotype is a singleton value; this sets the given phenotype for each of the specified traits in each target individual.  In the second pattern, phenotype is of length equal to the number of specified traits; this sets the phenotype for each of the specified traits to the corresponding phenotype in each target individual.  In the third pattern, phenotype is of length equal to the number of specified traits times the number of target individuals; this uses phenotype to provide a different phenotype for each trait in each individual, using consecutive values from phenotype to set the phenotype for each of the specified traits in one individual before moving to the next individual.

@@ -721,6 +722,7 @@

+ (void)setEffectForTrait([Nio<Trait> trait = NULL], [Nif effect = NULL])

Sets the mutation’s effect(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter effect must follow one of four patterns.  In the first pattern, effect is NULL; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation.  (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.)  In the second pattern, effect is a singleton value; this sets the given effect for each of the specified traits in each target mutation.  In the third pattern, effect is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation.  In the fourth pattern, effect is of length equal to the number of specified traits times the number of target mutations; this uses effect to provide a different effect value for each trait in each mutation, using consecutive values from effect to set the effect for each of the specified traits in one mutation before moving to the next mutation.

+

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  This policy applies to mutational effects, so the final effect of each mutation (perhaps influenced by a dominance coefficient) will be clamped prior to use in phenotype calculations.

+ (void)setHemizygousDominanceForTrait([Nio<Trait> trait = NULL], [Nif dominance = NULL])

Sets the mutation’s hemizygous dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default hemizygous dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation hemizygous dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default hemizygous dominance values.)  In the second pattern, dominance is a singleton value; this sets the given hemizygous dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the hemizygous dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different hemizygous dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the hemizygous dominance for each of the specified traits in one mutation before moving to the next mutation.

@@ -1381,6 +1383,7 @@

5.19.1  Trait properties

baselineOffset <–> (float$)

The baseline offset for the trait.  This value is combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.

+

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  A baseline offset value set through this property may therefore not be the value used by SLiM or subsequently returned by this property.

directFitnessEffect <–> (logical$)

A logical flag indicating whether the trait has a direct fitness effect or not.  If T, the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component.  If F, the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a “fitness function”), or the trait might have other effects that are not obviously related to fitness at all.

index => (integer$)

diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 47c9d932..2b7990af 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -148,8 +148,8 @@

Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the Individual or Species classes.  These requirements are necessary because, after the new trait is created, new properties added to the Individual and Species classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named heightT, getting and setting an individual’s trait value would be possible through the property individual.heightT.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.heightT would provide the Trait object named heightT.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("heightT", heightT) would allow the Trait object to be referenced simply as heightT.  For clarity, it is suggested that trait names end in a "T" to differentiate them from other variables and properties, but this is not required.

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).  The shorter versions "mul" and "add" are also allowed.

-

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive traits, such that the baseline offset has no effect upon the trait value.

-

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  As for the baseline offset, the individual offset mean defaults (if NULL is passed) to 1.0 for multiplicative traits, 0.0 for additive traits, to produce no effect.  The default standard deviation for the individual offset, if NULL is passed, is 0.0.  If NULL is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.

+

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive traits, such that the baseline offset has no effect upon the trait value.  Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of 0.0.

+

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  As for the baseline offset, the individual offset mean defaults (if NULL is passed) to 1.0 for multiplicative traits, 0.0 for additive traits, to produce no effect.  The default standard deviation for the individual offset, if NULL is passed, is 0.0.  If NULL is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.  Note that for multiplicative traits all effects, including individual offsets, will be clamped to a minimum of 0.0.

Finally, the directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

The use of the initializeTrait() function is optional.  If it is not called, a new Trait object will be created automatically, with a name generated from the species name plus a "T"; typically, then, the name is simT, except in multispecies models.  This default trait is configured to be multiplicative, with default values for the other parameters except directFitnessEffect, which is T for the default trait.  This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.2.  The creation of the default trait occurs as a side effect of the first call to initializeMutationType(), if initializeTrait() has not already been called.

(void)initializeTreeSeq([logical$ recordMutations = T], [Nif$ simplificationRatio = NULL], [Ni$ simplificationInterval = NULL], [logical$ checkCoalescence = F], [logical$ runCrosschecks = F], [logical$ retainCoalescentOnly = T], [Ns$ timeUnit = NULL])

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 82aa2d8c..3f363f64 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -4247,6 +4247,13 @@ The parameter \f4\fs20 to provide a different offset value for each trait in each individual, using consecutive values from \f3\fs18 offset \f4\fs20 to set the offset for each of the specified traits in one individual before moving to the next individual.\ +Note that for multiplicative traits, all effects are clamped to a minimum of +\f3\fs18 0.0 +\f4\fs20 as documented in the +\f3\fs18 Trait +\f4\fs20 class. This policy applies to individual offsets, so an offset value passed in or generated here may not be the value actually used by SLiM or subsequently returned by +\f3\fs18 offsetForTrait() +\f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 +\'a0(void)setPhenotypeForTrait(Nio\'a0trait, numeric\'a0phenotype)\ @@ -6344,6 +6351,12 @@ The parameter \f4\fs20 to provide a different effect value for each trait in each mutation, using consecutive values from \f3\fs18 effect \f4\fs20 to set the effect for each of the specified traits in one mutation before moving to the next mutation.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 Note that for multiplicative traits, all effects are clamped to a minimum of +\f3\fs18 0.0 +\f4\fs20 as documented in the +\f3\fs18 Trait +\f4\fs20 class. This policy applies to mutational effects, so the final effect of each mutation (perhaps influenced by a dominance coefficient) will be clamped prior to use in phenotype calculations.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 +\'a0(void)setHemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ @@ -14246,6 +14259,11 @@ nucleotide => (string$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 The baseline offset for the trait. This value is combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.\ +Note that for multiplicative traits, all effects are clamped to a minimum of +\f3\fs18 0.0 +\f4\fs20 as documented in the +\f3\fs18 Trait +\f4\fs20 class. A baseline offset value set through this property may therefore not be the value used by SLiM or subsequently returned by this property.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 directFitnessEffect <\'96> (logical$)\ diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 21979187..f2253be5 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -1549,7 +1549,9 @@ The \f1\fs18 1.0 \f2\fs20 for multiplicative traits, \f1\fs18 0.0 -\f2\fs20 for additive traits, such that the baseline offset has no effect upon the trait value.\ +\f2\fs20 for additive traits, such that the baseline offset has no effect upon the trait value. Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of +\f1\fs18 0.0 +\f2\fs20 .\ The \f1\fs18 individualOffsetMean \f2\fs20 and @@ -1566,7 +1568,9 @@ The \f1\fs18 0.0 \f2\fs20 . If \f1\fs18 NULL -\f2\fs20 is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.\ +\f2\fs20 is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not. Note that for multiplicative traits all effects, including individual offsets, will be clamped to a minimum of +\f1\fs18 0.0 +\f2\fs20 .\ Finally, the \f1\fs18 directFitnessEffect \f2\fs20 parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual. This will typically be diff --git a/VERSIONS b/VERSIONS index 8cd94955..130e2900 100644 --- a/VERSIONS +++ b/VERSIONS @@ -110,6 +110,7 @@ multitrait branch: fix #567, plot windows can have their aspect ratio distorted due to screen size and other constraints add Individual class method +(void)demandPhenotype([Nio trait = NULL], [l$ forceRecalc = F]) extend initializeMutationType() and initializeMutationTypeNuc() to make the effect distribution optional with [Ns$ distributionType = NULL], where NULL is equivalent to `"f", 0.0` + make setOffsetForTrait() clamp values to 0.0 for multiplicative traits; fix doc to discuss clamping of effects version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index f794e883..18b5c74d 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -4340,13 +4340,30 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin for (int64_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; - slim_effect_t offset = trait->DrawIndividualOffset(); - for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + if (trait->Type() == TraitType::kMultiplicative) { - Individual *ind = individuals_buffer[individual_index]; - - ind->trait_info_[trait_index].offset_ = offset; + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + slim_effect_t offset = trait->DrawIndividualOffset(); + + // effects for multiplicative traits are clamped to a minimum of 0.0 + if (offset < 0.0) + offset = 0.0; + + ind->trait_info_[trait_index].offset_ = offset; + } + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + ind->trait_info_[trait_index].offset_ = trait->DrawIndividualOffset(); + } } } } @@ -4355,23 +4372,17 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin // pattern 2: setting a single offset value across one or more traits in one or more individuals slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(0, nullptr)); - if (trait_count == 1) + for (int64_t trait_index : trait_indices) { - // optimized case for one trait - int64_t trait_index = trait_indices[0]; + Trait *trait = species->Traits()[trait_index]; + slim_effect_t offset_for_trait = offset; + + // effects for multiplicative traits are clamped to a minimum of 0.0 + if ((trait->Type() == TraitType::kMultiplicative) && (offset < 0.0)) + offset = 0.0; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; - } - else - { - for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - { - Individual *ind = individuals_buffer[individual_index]; - - for (int64_t trait_index : trait_indices) - ind->trait_info_[trait_index].offset_ = offset; - } + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset_for_trait; } } else if (offset_count == trait_count) @@ -4381,8 +4392,13 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin for (int64_t trait_index : trait_indices) { + Trait *trait = species->Traits()[trait_index]; slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(offset_index++, nullptr)); + // effects for multiplicative traits are clamped to a minimum of 0.0 + if ((trait->Type() == TraitType::kMultiplicative) && (offset < 0.0)) + offset = 0.0; + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { Individual *ind = individuals_buffer[individual_index]; @@ -4404,9 +4420,30 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { // optimized case for one trait int64_t trait_index = trait_indices[0]; + Trait *trait = species->Traits()[trait_index]; - for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = static_cast(*(offsets_int++)); + if (trait->Type() == TraitType::kMultiplicative) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + slim_effect_t offset = static_cast(*(offsets_int++)); + + // effects for multiplicative traits are clamped to a minimum of 0.0 + if (offset < 0.0) + offset = 0.0; + + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; + } + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + slim_effect_t offset = static_cast(*(offsets_int++)); + + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; + } + } } else { @@ -4415,7 +4452,16 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin Individual *ind = individuals_buffer[individual_index]; for (int64_t trait_index : trait_indices) - ind->trait_info_[trait_index].offset_ = static_cast(*(offsets_int++)); + { + Trait *trait = species->Traits()[trait_index]; + slim_effect_t offset = static_cast(*(offsets_int++)); + + // effects for multiplicative traits are clamped to a minimum of 0.0 + if ((trait->Type() == TraitType::kMultiplicative) && (offset < 0.0)) + offset = 0.0; + + ind->trait_info_[trait_index].offset_ = offset; + } } } } @@ -4428,9 +4474,30 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { // optimized case for one trait int64_t trait_index = trait_indices[0]; + Trait *trait = species->Traits()[trait_index]; - for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = static_cast(*(offsets_float++)); + if (trait->Type() == TraitType::kMultiplicative) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + slim_effect_t offset = static_cast(*(offsets_float++)); + + // effects for multiplicative traits are clamped to a minimum of 0.0 + if (offset < 0.0) + offset = 0.0; + + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; + } + } + else + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + slim_effect_t offset = static_cast(*(offsets_float++)); + + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; + } + } } else { @@ -4439,7 +4506,16 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin Individual *ind = individuals_buffer[individual_index]; for (int64_t trait_index : trait_indices) - ind->trait_info_[trait_index].offset_ = static_cast(*(offsets_float++)); + { + Trait *trait = species->Traits()[trait_index]; + slim_effect_t offset = static_cast(*(offsets_float++)); + + // effects for multiplicative traits are clamped to a minimum of 0.0 + if ((trait->Type() == TraitType::kMultiplicative) && (offset < 0.0)) + offset = 0.0; + + ind->trait_info_[trait_index].offset_ = offset; + } } } } @@ -6386,7 +6462,7 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos { effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); - if (effect <= 0.0) { // not clipped to zero, so we check here + if (effect <= 0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6495,7 +6571,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6530,7 +6606,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6579,7 +6655,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (homozygous_effect <= 0.0) { // not clipped to zero, so we check here + if (homozygous_effect <= 0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6611,7 +6687,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6666,7 +6742,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6718,7 +6794,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6748,7 +6824,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clipped to zero, so we check here + if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } From 3084dd95edde290da7a90ea8ef7642fe41be578f Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 25 Dec 2025 21:03:36 -0600 Subject: [PATCH 044/107] extend `Nio trait` to `Niso trait` --- QtSLiM/help/SLiMHelpClasses.html | 88 +++++++++--------- QtSLiM/help/SLiMHelpFunctions.html | 2 +- SLiMgui/SLiMHelpClasses.rtf | 144 +++++++++++++++++++---------- SLiMgui/SLiMHelpFunctions.rtf | 45 +++++---- VERSIONS | 1 + core/individual.cpp | 20 ++-- core/mutation.cpp | 24 ++--- core/mutation_type.cpp | 32 +++---- core/species.cpp | 18 +++- core/substitution.cpp | 12 +-- eidos/eidos_call_signature.cpp | 8 ++ eidos/eidos_call_signature.h | 8 ++ 12 files changed, 246 insertions(+), 156 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 81891509..50b38bc9 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -456,8 +456,8 @@

Returns a logical vector indicating whether each of the mutations in mutations is present in the individual (in any of its haplosomes); each element in the returned vector indicates whether the corresponding mutation is present (T) or absent (F).  This method is provided for speed; it is much faster than the corresponding Eidos code.

 (integer$)countOfMutationsOfType(io<MutationType>$ mutType)

Returns the number of mutations that are of the type specified by mutType, out of all of the mutations in the individual (in all of its haplosomes; a mutation that is present in both homologous haplosomes counts twice).  If you need a vector of the matching Mutation objects, rather than just a count, you should probably use mutationsFromHaplosomes().  This method is provided for speed; it is much faster than the corresponding Eidos code.

-

+ (void)demandPhenotype([Nio<Trait> trait = NULL], [logical$ forceRecalc = F])

-

Expresses “demand” for the phenotypes (trait values) of the trait(s) specified by trait, for the target vector of individuals.  This triggers the recalculation of those trait values in those individuals, as needed.  More specifically, if forceRecalc is F (the default), the specified trait values will only be recalculated if their current value is NAN; if they have any other value, they are considered to already be calculated, and will not be recalculated.  If forceRecalc is T, the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date.  No value is returned by demandPhenotype(), since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.

+

+ (void)demandPhenotype([Niso<Trait> trait = NULL], [logical$ forceRecalc = F])

+

Expresses “demand” for the phenotypes (trait values) of the trait(s) specified by trait, for the target vector of individuals.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  This triggers the recalculation of those trait values in those individuals, as needed.  More specifically, if forceRecalc is F (the default), the specified trait values will only be recalculated if their current value is NAN; if they have any other value, they are considered to already be calculated, and will not be recalculated.  If forceRecalc is T, the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date.  No value is returned by demandPhenotype(), since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.

This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set.  Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual.  The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.

Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so demandPhenotype() does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.  See sections 24.6 and 25.3 for details on fitness recalculation.

– (object<Haplosome>)haplosomesForChromosomes([Niso<Chromosome> chromosomes = NULL], [Ni$ index = NULL], [logical$ includeNulls = T])

@@ -470,10 +470,10 @@

The chromosomes parameter may be NULL, or may provide a vector of chromosomes specified by their integer id, string symbol, or with the Chromosome object itself.  If chromosomes is NULL (the default), mutations associated with every chromosome are returned; no filtering by chromosome is done.  Otherwise, only mutations associated with the specified chromosomes will be returned.

The returned vector will contain tranches of mutations, one tranche per chromosome, in the order that the chromosomes were specified (if chromosomes is non-NULL) or the order the chromosomes were defined in the model (if chromosomes is NULL).  Within a given tranche, the mutations for that chromosome will be returned in sorted order by position.  (If more than one mutation associated with a given chromosome exists at the same position, the order in which those mutations are returned is undefined.)

This method replaces the deprecated method uniqueMutationsOfType(), while providing additional useful options.  It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.

-

– (float)offsetForTrait([Nio<Trait> trait = NULL])

-

Returns the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

-

– (float)phenotypeForTrait([Nio<Trait> trait = NULL])

-

Returns the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)offsetForTrait([Niso<Trait> trait = NULL])

+

Returns the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)phenotypeForTrait([Niso<Trait> trait = NULL])

+

Returns the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+ (void)outputIndividuals([Ns$ filePath = NULL], [logical$ append = F], [Niso<Chromosome>$ chromosome = NULL], [logical$ spatialPositions = T], [logical$ ages = T], [logical$ ancestralNucleotides = F], [logical$ pedigreeIDs = F], [logical$ objectTags = F])

Output the state of the target vector of individuals in SLiM's own format.  If the optional parameter filePath is NULL (the default), output will be sent to Eidos’s output stream.  Otherwise, output will be sent to the filesystem path specified by filePath, overwriting that file if append if F, or appending to the end of it if append is T.  This method is quite similar to the Species method outputFull(), but (1) it can produce output for any vector of individuals, not always for the entire population; (2) it does not support output in a binary format; (3) it can produce output regarding the genetics for all chromosomes or for just one focal chromosome; and (4) there is no corresponding read method, as readFromPopulationFile() can read the data saved by outputFull().

The chromosome parameter specifies a focal chromosome for which the genetics of the target individuals will be output.  If chromosome is NULL, all chromosomes will be output; otherwise, chromosome may specify the focal chromosome with an integer chromosome id, a string chromosome symbol, or a Chromosome object.

@@ -501,12 +501,12 @@

Note that this relatedness is simply pedigree-based relatedness, and does not necessarily correspond to genetic relatedness, because of the effects of factors like assortment and recombination.  If a metric of actual genetic relatedness is desired, tree-sequence recording can be used after simulation is complete, to compute the exact genetic relatedness between individuals based upon the complete ancestry tree (a topic which is beyond the scope of this manual).  Actual genetic relatedness cannot presently be calculated during a simulation run; the information is implicitly contained in the recorded tree-sequence tables, but calculating it is too computationally expensive to be reasonable.

This method assumes that the grandparents (or the parents, if grandparental information is not available) are themselves unrelated and that they are not inbred; this assumption is necessary because we have no information about their parentage, since SLiM’s pedigree tracking information only goes back two generations.  Be aware that in a model where inbreeding or selfing occurs at all (including “incidental selfing”, where a hermaphroditic individual happens to choose itself as a mate), some level of “background relatedness” will be present and this assumption will be violated.  In such circumstances, relatedness() will therefore tend to underestimate the degree of relatedness between individuals, and the greater the degree of inbreeding, the greater the underestimation will be.  If inbreeding is allowed in a model – and particularly if it is common – the results of relatedness() should therefore not be taken as an estimate of absolute relatedness, but can still be useful as an estimate of relative relatedness (indicating that, say, A appears from the information available to be more closely related to B than it is to C).

See also sharedParentCount() for a different metric of relatedness.

-

+ (void)setOffsetForTrait([Nio<Trait> trait = NULL], [Nif offset = NULL])

-

Sets the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

+ (void)setOffsetForTrait([Niso<Trait> trait = NULL], [Nif offset = NULL])

+

Sets the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter offset must follow one of four patterns.  In the first pattern, offset is NULL; this draws the offset for each of the specified traits from each trait’s individual-offset distribution (defined by each trait’s individualOffsetMean and individualOffsetSD properties) in each target individual.  (Note that individual offsets are automatically drawn from these distributions when an individual is created; this re-draws new offset values.)  In the second pattern, offset is a singleton value; this sets the given offset for each of the specified traits in each target individual.  In the third pattern, offset is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual.  In the fourth pattern, offset is of length equal to the number of specified traits times the number of target individuals; this uses offset to provide a different offset value for each trait in each individual, using consecutive values from offset to set the offset for each of the specified traits in one individual before moving to the next individual.

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  This policy applies to individual offsets, so an offset value passed in or generated here may not be the value actually used by SLiM or subsequently returned by offsetForTrait().

-

+ (void)setPhenotypeForTrait(Nio<Trait> trait, numeric phenotype)

-

Sets the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

+ (void)setPhenotypeForTrait(Niso<Trait> trait, numeric phenotype)

+

Sets the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter phenotype must follow one of three patterns.  In the first pattern, phenotype is a singleton value; this sets the given phenotype for each of the specified traits in each target individual.  In the second pattern, phenotype is of length equal to the number of specified traits; this sets the phenotype for each of the specified traits to the corresponding phenotype in each target individual.  In the third pattern, phenotype is of length equal to the number of specified traits times the number of target individuals; this uses phenotype to provide a different phenotype for each trait in each individual, using consecutive values from phenotype to set the phenotype for each of the specified traits in one individual before moving to the next individual.

+ (void)setSpatialPosition(float position)

Sets the spatial position of the individual (as accessed through the spatialPosition property).  The length of position (the number of coordinates in the spatial position of an individual) depends upon the spatial dimensionality declared with initializeSLiMOptions().  If the spatial dimensionality is zero (as it is by default), it is an error to call this method.  The elements of position are set into the values of the x, y, and z properties (if those properties are encompassed by the spatial dimensionality of the simulation).  In other words, if the declared dimensionality is "xy", calling individual.setSpatialPosition(c(1.0, 0.5)) property is equivalent to individual.x = 1.0; individual.y = 0.5; individual.z is not set (even if a third value is supplied in position) since it is not encompassed by the simulation’s dimensionality in this example.

@@ -710,21 +710,21 @@

tag <–> (integer$)

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.

5.10.2  Mutation methods

-

– (float)dominanceForTrait([Nio<Trait> trait = NULL])

-

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

-

– (float)effectForTrait([Nio<Trait> trait = NULL])

-

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

-

– (float)hemizygousDominanceForTrait([Nio<Trait> trait = NULL])

-

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

-

+ (void)setDominanceForTrait([Nio<Trait> trait = NULL], [Nif dominance = NULL])

-

Sets the mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

+

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)effectForTrait([Niso<Trait> trait = NULL])

+

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

+

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

+ (void)setDominanceForTrait([Niso<Trait> trait = NULL], [Nif dominance = NULL])

+

Sets the mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.)  In the second pattern, dominance is a singleton value; this sets the given dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the dominance for each of the specified traits in one mutation before moving to the next mutation.

-

+ (void)setEffectForTrait([Nio<Trait> trait = NULL], [Nif effect = NULL])

-

Sets the mutation’s effect(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

+ (void)setEffectForTrait([Niso<Trait> trait = NULL], [Nif effect = NULL])

+

Sets the mutation’s effect(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter effect must follow one of four patterns.  In the first pattern, effect is NULL; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation.  (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.)  In the second pattern, effect is a singleton value; this sets the given effect for each of the specified traits in each target mutation.  In the third pattern, effect is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation.  In the fourth pattern, effect is of length equal to the number of specified traits times the number of target mutations; this uses effect to provide a different effect value for each trait in each mutation, using consecutive values from effect to set the effect for each of the specified traits in one mutation before moving to the next mutation.

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  This policy applies to mutational effects, so the final effect of each mutation (perhaps influenced by a dominance coefficient) will be clamped prior to use in phenotype calculations.

-

+ (void)setHemizygousDominanceForTrait([Nio<Trait> trait = NULL], [Nif dominance = NULL])

-

Sets the mutation’s hemizygous dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

+ (void)setHemizygousDominanceForTrait([Niso<Trait> trait = NULL], [Nif dominance = NULL])

+

Sets the mutation’s hemizygous dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default hemizygous dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation hemizygous dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default hemizygous dominance values.)  In the second pattern, dominance is a singleton value; this sets the given hemizygous dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the hemizygous dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different hemizygous dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the hemizygous dominance for each of the specified traits in one mutation before moving to the next mutation.

– (void)setMutationType(io<MutationType>$ mutType)

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  The effects and dominance coefficients of existing mutations are not changed, since those are properties of the mutation objects themselves; they can be changed explicitly using the setEffectForTrait() and setDominanceForTrait() methods of Mutation if so desired.

@@ -757,26 +757,26 @@

tag <–> (integer$)

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.  See also the getValue() and setValue() methods (provided by the Dictionary class; see the Eidos manual), for another way of attaching state to mutation types.

5.11.2  MutationType methods

-

– (float)defaultDominanceForTrait([Nio<Trait> trait = NULL])

-

Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their dominance property, but that can be changed later with the Mutation method setDominanceForTrait().

+

– (float)defaultDominanceForTrait([Niso<Trait> trait = NULL])

+

Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their dominance property, but that can be changed later with the Mutation method setDominanceForTrait().

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

-

– (float)defaultHemizygousDominanceForTrait([Nio<Trait> trait = NULL])

-

Returns the default hemizygous dominance coefficient used for the specified trait or traits, for mutations of this type when hemizygous (i.e., when present in only one copy because they are facing a null haplosome, such as for an X chromosome in a male).  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default hemizygous dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their hemizygousDominance property, but that can be changed later with the Mutation method setHemizygousDominanceForTrait().

+

– (float)defaultHemizygousDominanceForTrait([Niso<Trait> trait = NULL])

+

Returns the default hemizygous dominance coefficient used for the specified trait or traits, for mutations of this type when hemizygous (i.e., when present in only one copy because they are facing a null haplosome, such as for an X chromosome in a male).  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default hemizygous dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their hemizygousDominance property, but that can be changed later with the Mutation method setHemizygousDominanceForTrait().

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

-

– (float)drawEffectForTrait([Nio<Trait> trait = NULL], [integer$ n = 1])

-

Draws and returns a vector of n mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  See the MutationType class documentation for discussion of the supported distributions and their uses.  If the distribution of effects is of type "s", this method will result in synchronous execution of the script associated with the distribution of effects.

-

– (fs)effectDistributionParamsForTrait([Nio<Trait> trait = NULL])

-

Returns the parameters that configure the distribution of effects for the specified trait or traits.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DES type "s", and type float for all other DES types.

-

– (string)effectDistributionTypeForTrait([Nio<Trait> trait = NULL])

-

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

-

– (void)setDefaultDominanceForTrait(Nio<Trait> trait, float dominance)

-

Set the default dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of defaultDominance is used for each corresponding trait).

-

– (void)setDefaultHemizygousDominanceForTrait(Nio<Trait> trait, float dominance)

-

Set the default hemizygous dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default hemizygous dominance for all specified traits), or must match the number of specified traits (in which case one element of dominance is used for each corresponding trait).

-

– (void)setEffectDistributionForTrait(Nio<Trait> trait, string$ distributionType, ...)

-

Set the distribution of effects for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.

+

– (float)drawEffectForTrait([Niso<Trait> trait = NULL], [integer$ n = 1])

+

Draws and returns a vector of n mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  See the MutationType class documentation for discussion of the supported distributions and their uses.  If the distribution of effects is of type "s", this method will result in synchronous execution of the script associated with the distribution of effects.

+

– (fs)effectDistributionParamsForTrait([Niso<Trait> trait = NULL])

+

Returns the parameters that configure the distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DES type "s", and type float for all other DES types.

+

– (string)effectDistributionTypeForTrait([Niso<Trait> trait = NULL])

+

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

+

– (void)setDefaultDominanceForTrait(Niso<Trait> trait, float dominance)

+

Set the default dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of defaultDominance is used for each corresponding trait).

+

– (void)setDefaultHemizygousDominanceForTrait(Niso<Trait> trait, float dominance)

+

Set the default hemizygous dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default hemizygous dominance for all specified traits), or must match the number of specified traits (in which case one element of dominance is used for each corresponding trait).

+

– (void)setEffectDistributionForTrait(Niso<Trait> trait, string$ distributionType, ...)

+

Set the distribution of effects for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.

The distributionType may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for the exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  See the MutationType class documentation for discussion of these distributions and their uses.  The distribution of effects for a mutation type is normally a constant in simulations, so be sure you know what you are doing.

5.12  Class Plot

5.12.1  Plot properties

@@ -1373,12 +1373,12 @@

tag <–> (integer$)

A user-defined integer value.  The value of tag is carried over automatically from the original Mutation object.  Apart from that, the value of tag is not used by SLiM; it is free for you to use.

5.18.2  Substitution methods

-

– (float)dominanceForTrait([Nio<Trait> trait = NULL])

-

Returns the substitution’s dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

-

– (float)effectForTrait([Nio<Trait> trait = NULL])

-

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

-

– (float)hemizygousDominanceForTrait([Nio<Trait> trait = NULL])

-

Returns the substitution’s hemizygous dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

+

Returns the substitution’s dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)effectForTrait([Niso<Trait> trait = NULL])

+

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

+

Returns the substitution’s hemizygous dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

5.19  Class Trait

5.19.1  Trait properties

baselineOffset <–> (float$)

diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 2b7990af..35b1de6a 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -146,7 +146,7 @@

The color parameter, if not "", sets a string color value used to represent the species in SLiMgui.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB" (see the Eidos manual for details).  If color is the empty string, "", SLiMgui will choose a default color.

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [logical$ directFitnessEffect = F])

Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

-

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the Individual or Species classes.  These requirements are necessary because, after the new trait is created, new properties added to the Individual and Species classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named heightT, getting and setting an individual’s trait value would be possible through the property individual.heightT.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.heightT would provide the Trait object named heightT.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("heightT", heightT) would allow the Trait object to be referenced simply as heightT.  For clarity, it is suggested that trait names end in a "T" to differentiate them from other variables and properties, but this is not required.

+

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the Individual, Species, Mutation, or Substitution classes.  These requirements are necessary because, after the new trait is created, new properties are added to those classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named height, getting and setting an individual’s trait value would be possible through the property individual.height.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.height would provide the Trait object named height.  See the Mutation and Substitution classes for details on the trait-related properties defined for them.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("height", height) would allow the Trait object to be referenced simply as height.

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).  The shorter versions "mul" and "add" are also allowed.

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive traits, such that the baseline offset has no effect upon the trait value.  Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of 0.0.

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  As for the baseline offset, the individual offset mean defaults (if NULL is passed) to 1.0 for multiplicative traits, 0.0 for additive traits, to produce no effect.  The default standard deviation for the individual offset, if NULL is passed, is 0.0.  If NULL is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.  Note that for multiplicative traits all effects, including individual offsets, will be clamped to a minimum of 0.0.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 3f363f64..8602029a 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -3648,12 +3648,20 @@ See the description of the \f4\fs20 . This method is provided for speed; it is much faster than the corresponding Eidos code.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 +\'a0(void)demandPhenotype([Nio\'a0trait\'a0=\'a0NULL], [logical$\'a0forceRecalc\'a0=\'a0F])\ +\f3\fs18 \cf2 +\'a0(void)demandPhenotype([Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0forceRecalc\'a0=\'a0F])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Expresses \'93demand\'94 for the phenotypes (trait values) of the trait(s) specified by \f3\fs18 trait -\f4\fs20 , for the target vector of individuals. This triggers the recalculation of those trait values in those individuals, as needed. More specifically, if +\f4\fs20 , for the target vector of individuals. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. This triggers the recalculation of those trait values in those individuals, as needed. More specifically, if \f3\fs18 forceRecalc \f4\fs20 is \f3\fs18 F @@ -3666,7 +3674,8 @@ See the description of the \f4\fs20 , the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date. No value is returned by \f3\fs18 demandPhenotype() \f4\fs20 , since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.\ -This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set. Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual. The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set. Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual. The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.\ Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so \f3\fs18 demandPhenotype() \f4\fs20 does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way. See sections 24.6 and 25.3 for details on fitness recalculation.\ @@ -3824,14 +3833,16 @@ This method replaces the deprecated method \f4\fs20 , while providing additional useful options. It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)offsetForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)offsetForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the individual offset(s) for the trait(s) specified by \f3\fs18 trait \f4\fs20 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -3840,14 +3851,16 @@ This method replaces the deprecated method \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)phenotypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)phenotypeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the individual phenotype(s) for the trait(s) specified by \f3\fs18 trait \f4\fs20 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -4214,14 +4227,16 @@ See also \f4\fs20 for a different metric of relatedness.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 +\'a0(void)setOffsetForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0offset\'a0=\'a0NULL])\ +\f3\fs18 \cf2 +\'a0(void)setOffsetForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0offset\'a0=\'a0NULL])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Sets the individual offset(s) for the trait(s) specified by \f3\fs18 trait \f4\fs20 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -4256,14 +4271,16 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 +\'a0(void)setPhenotypeForTrait(Nio\'a0trait, numeric\'a0phenotype)\ +\f3\fs18 \cf2 +\'a0(void)setPhenotypeForTrait(Niso\'a0trait, numeric\'a0phenotype)\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Sets the individual phenotype(s) for the trait(s) specified by \f3\fs18 trait \f4\fs20 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6234,7 +6251,7 @@ If you don\'92t care which subpopulation a mutation originated in, the \f1\fs22 methods\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 \'96\'a0(float)dominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\i0\fs18 \cf2 \'96\'a0(float)dominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the mutation\'92s dominance coefficient for the trait(s) specified by @@ -6243,7 +6260,9 @@ If you don\'92t care which subpopulation a mutation originated in, the \f1\i h \f4\i0 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6252,7 +6271,7 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the mutation\'92s effect size for the trait(s) specified by @@ -6263,7 +6282,9 @@ If you don\'92t care which subpopulation a mutation originated in, the \f1\i a \f4\i0 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6272,7 +6293,7 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the mutation\'92s hemizygous dominance coefficient for the trait(s) specified by @@ -6282,7 +6303,9 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\i0\fs13\fsmilli6667 \sub hemi \fs20 \nosupersub . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6291,14 +6314,16 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 +\'a0(void)setDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ +\f3\fs18 \cf2 +\'a0(void)setDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Sets the mutation\'92s dominance coefficient(s) for the trait(s) specified by \f3\fs18 trait \f4\fs20 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6322,14 +6347,16 @@ The parameter \f4\fs20 to set the dominance for each of the specified traits in one mutation before moving to the next mutation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 +\'a0(void)setEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0effect\'a0=\'a0NULL])\ +\f3\fs18 \cf2 +\'a0(void)setEffectForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0effect\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Sets the mutation\'92s effect(s) for the trait(s) specified by \f3\fs18 trait \f4\fs20 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6351,22 +6378,23 @@ The parameter \f4\fs20 to provide a different effect value for each trait in each mutation, using consecutive values from \f3\fs18 effect \f4\fs20 to set the effect for each of the specified traits in one mutation before moving to the next mutation.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 Note that for multiplicative traits, all effects are clamped to a minimum of +Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 0.0 \f4\fs20 as documented in the \f3\fs18 Trait \f4\fs20 class. This policy applies to mutational effects, so the final effect of each mutation (perhaps influenced by a dominance coefficient) will be clamped prior to use in phenotype calculations.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 +\'a0(void)setHemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ +\f3\fs18 \cf2 +\'a0(void)setHemizygousDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Sets the mutation\'92s hemizygous dominance coefficient(s) for the trait(s) specified by \f3\fs18 trait \f4\fs20 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6614,12 +6642,14 @@ The species to which the target object belongs.\ \f1\fs22 methods\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 \'96\'a0(float)defaultDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\i0\fs18 \cf2 \'96\'a0(float)defaultDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous. The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6640,12 +6670,14 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 type normally used by Eidos. This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)defaultHemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)defaultHemizygousDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the default hemizygous dominance coefficient used for the specified trait or traits, for mutations of this type when hemizygous (i.e., when present in only one copy because they are facing a null haplosome, such as for an X chromosome in a male). The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6666,14 +6698,16 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 type normally used by Eidos. This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)drawEffectForTrait([Nio\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ +\f3\fs18 \cf2 \'96\'a0(float)drawEffectForTrait([Niso\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Draws and returns a vector of \f3\fs18 n \f4\fs20 mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type. The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6684,12 +6718,14 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 , this method will result in synchronous execution of the script associated with the distribution of effects.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(fs)effectDistributionParamsForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(fs)effectDistributionParamsForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the parameters that configure the distribution of effects for the specified trait or traits. The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6702,12 +6738,14 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 for all other DES types.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(string)effectDistributionTypeForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(string)effectDistributionTypeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the type of distribution of effects for the specified trait or traits. The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6730,12 +6768,14 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 class documentation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(void)setDefaultDominanceForTrait(Nio\'a0trait, float\'a0dominance)\ +\f3\fs18 \cf2 \'96\'a0(void)setDefaultDominanceForTrait(Niso\'a0trait, float\'a0dominance)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Set the default dominance coefficient for a specified trait or traits, for the target mutation type. The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6746,12 +6786,14 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 is used for each corresponding trait).\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(void)setDefaultHemizygousDominanceForTrait(Nio\'a0trait, float\'a0dominance)\ +\f3\fs18 \cf2 \'96\'a0(void)setDefaultHemizygousDominanceForTrait(Niso\'a0trait, float\'a0dominance)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Set the default hemizygous dominance coefficient for a specified trait or traits, for the target mutation type. The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -6762,12 +6804,14 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 is used for each corresponding trait).\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(void)setEffectDistributionForTrait(Nio\'a0trait, string$\'a0distributionType, ...)\ +\f3\fs18 \cf2 \'96\'a0(void)setEffectDistributionForTrait(Niso\'a0trait, string$\'a0distributionType, ...)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Set the distribution of effects for a specified trait or traits, for the target mutation type. The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -14189,7 +14233,7 @@ nucleotide => (string$)\ \f1\fs22 methods\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 \'96\'a0(float)dominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\i0\fs18 \cf2 \'96\'a0(float)dominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the substitution\'92s dominance coefficient for the trait(s) specified by @@ -14198,7 +14242,9 @@ nucleotide => (string$)\ \f1\i h \f4\i0 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -14208,7 +14254,7 @@ nucleotide => (string$)\ \f5\fs22 \cf0 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the substitution\'92s effect size for the trait(s) specified by @@ -14219,7 +14265,9 @@ nucleotide => (string$)\ \f1\i a \f4\i0 . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL @@ -14228,7 +14276,7 @@ nucleotide => (string$)\ \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Nio\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the substitution\'92s hemizygous dominance coefficient for the trait(s) specified by @@ -14238,7 +14286,9 @@ nucleotide => (string$)\ \f4\i0\fs13\fsmilli6667 \sub hemi \fs20 \nosupersub . The traits can be specified as \f3\fs18 integer -\f4\fs20 indices of traits in the species, or directly as +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index f2253be5..4a783a70 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -961,7 +961,8 @@ The \f2\fs20 is assumed and must not be supplied. See the \f1\fs18 MutationType \f2\fs20 class documentation for discussion of the various DESs and their uses.\ -\expnd0\expndtw0\kerning0 +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 \expnd0\expndtw0\kerning0 Note that by default in WF models, all mutations of a given mutation type will be converted into \f1\fs18 Substitution \f2\fs20 objects when they reach fixation, for efficiency reasons. If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the @@ -1342,6 +1343,7 @@ If \f1\fs18 initializeChromosome() \f2\fs20 , allowing a different mutation run count to be specified for each chromosome in multi-chromosome models.\expnd0\expndtw0\kerning0 \ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 \kerning1\expnd0\expndtw0 If \f1\fs18 preventIncidentalSelfing \f2\fs20 is @@ -1421,6 +1423,7 @@ If \f2\fs20 for \f1\fs18 checkInfiniteLoops \f2\fs20 to disable these checks. There is no way to turn these checks on or off for individual loops; it is a global setting.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 This function will likely be extended with further options in the future, added on to the end of the argument list. Using named arguments with this call is recommended for readability. Note that turning on optional features may increase the runtime and memory footprint of SLiM.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -1493,42 +1496,46 @@ The \f2\fs20 object is returned. For more details on the way that traits work in SLiM, beyond what is given below, see the \f1\fs18 Trait \f2\fs20 class documentation.\ -The +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 The \f1\fs18 name \f2\fs20 parameter gives the name of the new trait. This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the \f1\fs18 Individual -\f2\fs20 or -\f1\fs18 Species -\f2\fs20 classes. These requirements are necessary because, after the new trait is created, new properties added to the -\f1\fs18 Individual -\f2\fs20 and +\f2\fs20 , \f1\fs18 Species -\f2\fs20 classes, with the same name as the new trait, for convenience. The new +\f2\fs20 , +\f1\fs18 Mutation +\f2\fs20 , or +\f1\fs18 Substitution +\f2\fs20 classes. These requirements are necessary because, after the new trait is created, new properties are added to those classes, with the same name as the new trait, for convenience. The new \f1\fs18 Individual \f2\fs20 property allows trait values to be accessed directly through a property; for example, if the new trait is named -\f1\fs18 heightT +\f1\fs18 height \f2\fs20 , getting and setting an individual\'92s trait value would be possible through the property -\f1\fs18 individual.heightT +\f1\fs18 individual.height \f2\fs20 . The new \f1\fs18 Species \f2\fs20 property allows traits themselves to be accessed directly through a property; continuing the previous example, -\f1\fs18 sim.heightT +\f1\fs18 sim.height \f2\fs20 would provide the \f1\fs18 Trait \f2\fs20 object named -\f1\fs18 heightT -\f2\fs20 . If desired, +\f1\fs18 height +\f2\fs20 . See the +\f1\fs18 Mutation +\f2\fs20 and +\f1\fs18 Substitution +\f2\fs20 classes for details on the trait-related properties defined for them. If desired, \f1\fs18 defineConstant() \f2\fs20 may also be used to set up a global constant for a trait; for example, -\f1\fs18 defineConstant("heightT", heightT) +\f1\fs18 defineConstant("height", height) \f2\fs20 would allow the \f1\fs18 Trait \f2\fs20 object to be referenced simply as -\f1\fs18 heightT -\f2\fs20 . For clarity, it is suggested that trait names end in a -\f1\fs18 "T" -\f2\fs20 to differentiate them from other variables and properties, but this is not required.\ -The +\f1\fs18 height +\f2\fs20 .\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 The \f1\fs18 type \f2\fs20 parameter gives the type of trait to be created, as a \f1\fs18 string diff --git a/VERSIONS b/VERSIONS index 130e2900..fa3b4b47 100644 --- a/VERSIONS +++ b/VERSIONS @@ -111,6 +111,7 @@ multitrait branch: add Individual class method +(void)demandPhenotype([Nio trait = NULL], [l$ forceRecalc = F]) extend initializeMutationType() and initializeMutationTypeNuc() to make the effect distribution optional with [Ns$ distributionType = NULL], where NULL is equivalent to `"f", 0.0` make setOffsetForTrait() clamp values to 0.0 for multiplicative traits; fix doc to discuss clamping of effects + extend all methods taking a `Nio trait` parameter to `Niso trait`, allowing traits to also be identified by string name version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index 18b5c74d..e5414b2a 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -3340,7 +3340,7 @@ EidosValue_SP Individual::ExecuteMethod_haplosomesForChromosomes(EidosGlobalStri return EidosValue_SP(vec); } -// ********************* - (float)offsetForTrait([Nio trait = NULL]) +// ********************* - (float)offsetForTrait([Niso trait = NULL]) // EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -3374,7 +3374,7 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met } } -// ********************* - (float)phenotypeForTrait([Nio trait = NULL]) +// ********************* - (float)phenotypeForTrait([Niso trait = NULL]) // EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -4263,11 +4263,11 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_countOfMutationsOfType, kEidosValueMaskInt | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_countOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_relatedness, kEidosValueMaskFloat))->AddObject("individuals", gSLiM_Individual_Class)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, "chromosome", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_haplosomesForChromosomes, kEidosValueMaskObject, gSLiM_Haplosome_Class))->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddInt_OSN("index", gStaticEidosValueNULL)->AddLogical_OS("includeNulls", gStaticEidosValue_LogicalT)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_phenotypeForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_demandPhenotype, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setPhenotypeForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddNumeric("phenotype")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_phenotypeForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_demandPhenotype, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setPhenotypeForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddNumeric("phenotype")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_uniqueMutationsOfType, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->MarkDeprecated()); @@ -4307,7 +4307,7 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ } } -// ********************* + (void)setOffsetForTrait([Nio trait = NULL], [Nif offset = NULL]) +// ********************* + (void)setOffsetForTrait([Niso trait = NULL], [Nif offset = NULL]) // EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { @@ -4526,7 +4526,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin return gStaticEidosValueVOID; } -// ********************* + (void)setPhenotypeForTrait([Nio trait = NULL], [Nif phenotype = NULL]) +// ********************* + (void)setPhenotypeForTrait([Niso trait = NULL], [Nif phenotype = NULL]) // EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { @@ -5881,7 +5881,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setSpatialPosition(EidosGlobalStri #pragma mark Phenotype demand #pragma mark - -// ********************* + (void)demandPhenotype([Nio trait = NULL], [l$ forceRecalc = F]) +// ********************* + (void)demandPhenotype([Niso trait = NULL], [l$ forceRecalc = F]) // EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { diff --git a/core/mutation.cpp b/core/mutation.cpp index 3d68c2b6..4af9ac55 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -1008,7 +1008,7 @@ EidosValue_SP Mutation::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, c } } -// ********************* - (float)effectForTrait([Nio trait = NULL]) +// ********************* - (float)effectForTrait([Niso trait = NULL]) // EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -1046,7 +1046,7 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho } } -// ********************* - (float)dominanceForTrait([Nio trait = NULL]) +// ********************* - (float)dominanceForTrait([Niso trait = NULL]) // EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -1084,7 +1084,7 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me } } -// ********************* - (float)hemizygousDominanceForTrait([Nio trait = NULL]) +// ********************* - (float)hemizygousDominanceForTrait([Niso trait = NULL]) // EidosValue_SP Mutation::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -1205,12 +1205,12 @@ const std::vector *Mutation_Class::Methods(void) const methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setMutationType, kEidosValueMaskVOID))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); @@ -1231,7 +1231,7 @@ EidosValue_SP Mutation_Class::ExecuteClassMethod(EidosGlobalStringID p_method_id } } -// ********************* + (void)setEffectForTrait([Nio trait = NULL], [Nif effect = NULL]) +// ********************* + (void)setEffectForTrait([Niso trait = NULL], [Nif effect = NULL]) // EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { @@ -1417,8 +1417,8 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI return gStaticEidosValueVOID; } -// ********************* + (void)setDominanceForTrait([Nio trait = NULL], [Nif dominance = NULL]) -// ********************* + (void)setHemizygousDominanceForTrait([Nio trait = NULL], [Nif dominance = NULL]) +// ********************* + (void)setDominanceForTrait([Niso trait = NULL], [Nif dominance = NULL]) +// ********************* + (void)setHemizygousDominanceForTrait([Niso trait = NULL], [Nif dominance = NULL]) // EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 244e3167..1ace2cb2 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -679,7 +679,7 @@ EidosValue_SP MutationType::ExecuteInstanceMethod(EidosGlobalStringID p_method_i } } -// ********************* - (float$)defaultDominanceForTrait([Nio trait = NULL]) +// ********************* - (float$)defaultDominanceForTrait([Niso trait = NULL]) // EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -707,7 +707,7 @@ EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalSt } } -// ********************* - (float$)defaultHemizygousDominanceForTrait([Nio trait = NULL]) +// ********************* - (float$)defaultHemizygousDominanceForTrait([Niso trait = NULL]) // EidosValue_SP MutationType::ExecuteMethod_defaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -735,7 +735,7 @@ EidosValue_SP MutationType::ExecuteMethod_defaultHemizygousDominanceForTrait(Eid } } -// ********************* - (fs)effectDistributionParamsForTrait([Nio trait = NULL]) +// ********************* - (fs)effectDistributionParamsForTrait([Niso trait = NULL]) // EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -793,7 +793,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos } } -// ********************* - (string$)effectDistributionTypeForTrait([Nio trait = NULL]) +// ********************* - (string$)effectDistributionTypeForTrait([Niso trait = NULL]) // EidosValue_SP MutationType::ExecuteMethod_effectDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -827,7 +827,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionTypeForTrait(EidosGl return EidosValue_SP(string_result); } -// ********************* - (float)drawEffectForTrait([Nio trait = NULL], [integer$ n = 1]) +// ********************* - (float)drawEffectForTrait([Niso trait = NULL], [integer$ n = 1]) // EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -865,7 +865,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID } } -// ********************* - (void)setDefaultDominanceForTrait(Nio trait, float dominance) +// ********************* - (void)setDefaultDominanceForTrait(Niso trait, float dominance) // EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -913,7 +913,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba return gStaticEidosValueVOID; } -// ********************* - (void)setDefaultHemizygousDominanceForTrait(Nio trait, float dominance) +// ********************* - (void)setDefaultHemizygousDominanceForTrait(Niso trait, float dominance) // EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -961,7 +961,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( return gStaticEidosValueVOID; } -// ********************* - (void)setEffectDistributionForTrait(Nio trait, string$ distributionType, ...) +// ********************* - (void)setEffectDistributionForTrait(Niso trait, string$ distributionType, ...) // EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -1055,14 +1055,14 @@ const std::vector *MutationType_Class::Methods(void) c methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultHemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectDistributionForTrait, kEidosValueMaskVOID))->AddIntObject_N("trait", gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultHemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectDistributionForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/species.cpp b/core/species.cpp index 55c75815..720cab00 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -604,7 +604,7 @@ int64_t Species::GetTraitIndexFromEidosValue(EidosValue *trait_value, const std: return trait_index; } -// This returns trait indices, represented by an EidosValue with integer indices or Trait objects, or NULL for all traits +// This returns trait indices, represented by an EidosValue with integer indices, string names, or Trait objects, or NULL for all traits void Species::GetTraitIndicesFromEidosValue(std::vector &trait_indices, EidosValue *traits_value, const std::string &p_method_name) { EidosValueType traits_value_type = traits_value->Type(); @@ -635,6 +635,22 @@ void Species::GetTraitIndicesFromEidosValue(std::vector &trait_indices, } break; } + case EidosValueType::kValueString: + { + const std::string *indices_data = traits_value->StringData(); + + for (int names_index = 0; names_index < traits_value_count; names_index++) + { + const std::string &trait_name = indices_data[names_index]; + Trait *trait = TraitFromName(trait_name); + + if (trait == nullptr) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndicesFromEidosValue): unrecognized trait name in " << p_method_name << "(); trait name " << trait_name << " is not defined for the species." << EidosTerminate(nullptr); + + trait_indices.push_back(trait->Index()); + } + break; + } case EidosValueType::kValueObject: { Trait * const *traits_data = (Trait * const *)traits_value->ObjectData(); diff --git a/core/substitution.cpp b/core/substitution.cpp index dd4885be..687b2781 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -549,7 +549,7 @@ EidosValue_SP Substitution::ExecuteInstanceMethod(EidosGlobalStringID p_method_i } } -// ********************* - (float)effectForTrait([Nio trait = NULL]) +// ********************* - (float)effectForTrait([Niso trait = NULL]) // EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -583,7 +583,7 @@ EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_m } } -// ********************* - (float)dominanceForTrait([Nio trait = NULL]) +// ********************* - (float)dominanceForTrait([Niso trait = NULL]) // EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -617,7 +617,7 @@ EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID } } -// ********************* - (float)hemizygousDominanceForTrait([Nio trait = NULL]) +// ********************* - (float)hemizygousDominanceForTrait([Niso trait = NULL]) // EidosValue_SP Substitution::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -702,9 +702,9 @@ const std::vector *Substitution_Class::Methods(void) c methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/eidos/eidos_call_signature.cpp b/eidos/eidos_call_signature.cpp index 0121e6d3..fede6234 100644 --- a/eidos/eidos_call_signature.cpp +++ b/eidos/eidos_call_signature.cpp @@ -208,6 +208,7 @@ EidosCallSignature *EidosCallSignature::AddLogicalEquiv(const std::string &p_arg EidosCallSignature *EidosCallSignature::AddAnyBase(const std::string &p_argument_name) { return AddArg(kEidosValueMaskAnyBase, p_argument_name, nullptr); } EidosCallSignature *EidosCallSignature::AddAny(const std::string &p_argument_name) { return AddArg(kEidosValueMaskAny, p_argument_name, nullptr); } EidosCallSignature *EidosCallSignature::AddIntObject(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskInt | kEidosValueMaskObject, p_argument_name, p_argument_class); } +EidosCallSignature *EidosCallSignature::AddIntStringObject(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject, p_argument_name, p_argument_class); } EidosCallSignature *EidosCallSignature::AddObject(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskObject, p_argument_name, p_argument_class); } EidosCallSignature *EidosCallSignature::AddLogical_O(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskLogical | kEidosValueMaskOptional, p_argument_name, nullptr, std::move(p_default_value)); } @@ -220,6 +221,7 @@ EidosCallSignature *EidosCallSignature::AddLogicalEquiv_O(const std::string &p_a EidosCallSignature *EidosCallSignature::AddAnyBase_O(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskAnyBase | kEidosValueMaskOptional, p_argument_name, nullptr, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddAny_O(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskAny | kEidosValueMaskOptional, p_argument_name, nullptr, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddIntObject_O(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskInt | kEidosValueMaskObject | kEidosValueMaskOptional, p_argument_name, p_argument_class, std::move(p_default_value)); } +EidosCallSignature *EidosCallSignature::AddIntStringObject_O(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, p_argument_name, p_argument_class, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddObject_O(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskObject | kEidosValueMaskOptional, p_argument_name, p_argument_class, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddLogical_S(const std::string &p_argument_name) { return AddArg(kEidosValueMaskLogical | kEidosValueMaskSingleton, p_argument_name, nullptr); } @@ -232,6 +234,7 @@ EidosCallSignature *EidosCallSignature::AddLogicalEquiv_S(const std::string &p_a EidosCallSignature *EidosCallSignature::AddAnyBase_S(const std::string &p_argument_name) { return AddArg(kEidosValueMaskAnyBase | kEidosValueMaskSingleton, p_argument_name, nullptr); } EidosCallSignature *EidosCallSignature::AddAny_S(const std::string &p_argument_name) { return AddArg(kEidosValueMaskAny | kEidosValueMaskSingleton, p_argument_name, nullptr); } EidosCallSignature *EidosCallSignature::AddIntObject_S(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskInt | kEidosValueMaskObject | kEidosValueMaskSingleton, p_argument_name, p_argument_class); } +EidosCallSignature *EidosCallSignature::AddIntStringObject_S(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskSingleton, p_argument_name, p_argument_class); } EidosCallSignature *EidosCallSignature::AddObject_S(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskObject | kEidosValueMaskSingleton, p_argument_name, p_argument_class); } EidosCallSignature *EidosCallSignature::AddLogical_OS(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskLogical | kEidosValueMaskOptional | kEidosValueMaskSingleton, p_argument_name, nullptr, std::move(p_default_value)); } @@ -244,6 +247,7 @@ EidosCallSignature *EidosCallSignature::AddLogicalEquiv_OS(const std::string &p_ EidosCallSignature *EidosCallSignature::AddAnyBase_OS(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskAnyBase | kEidosValueMaskOptional | kEidosValueMaskSingleton, p_argument_name, nullptr, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddAny_OS(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskAny | kEidosValueMaskOptional | kEidosValueMaskSingleton, p_argument_name, nullptr, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddIntObject_OS(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskInt | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, p_argument_name, p_argument_class, std::move(p_default_value)); } +EidosCallSignature *EidosCallSignature::AddIntStringObject_OS(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, p_argument_name, p_argument_class, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddObject_OS(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, p_argument_name, p_argument_class, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddLogical_N(const std::string &p_argument_name) { return AddArg(kEidosValueMaskLogical | kEidosValueMaskNULL, p_argument_name, nullptr); } @@ -254,6 +258,7 @@ EidosCallSignature *EidosCallSignature::AddString_N(const std::string &p_argumen EidosCallSignature *EidosCallSignature::AddNumeric_N(const std::string &p_argument_name) { return AddArg(kEidosValueMaskNumeric | kEidosValueMaskNULL, p_argument_name, nullptr); } EidosCallSignature *EidosCallSignature::AddLogicalEquiv_N(const std::string &p_argument_name) { return AddArg(kEidosValueMaskLogicalEquiv | kEidosValueMaskNULL, p_argument_name, nullptr); } EidosCallSignature *EidosCallSignature::AddIntObject_N(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskInt | kEidosValueMaskObject | kEidosValueMaskNULL, p_argument_name, p_argument_class); } +EidosCallSignature *EidosCallSignature::AddIntStringObject_N(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskNULL, p_argument_name, p_argument_class); } EidosCallSignature *EidosCallSignature::AddObject_N(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskObject | kEidosValueMaskNULL, p_argument_name, p_argument_class); } EidosCallSignature *EidosCallSignature::AddLogical_ON(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskLogical | kEidosValueMaskOptional | kEidosValueMaskNULL, p_argument_name, nullptr, std::move(p_default_value)); } @@ -264,6 +269,7 @@ EidosCallSignature *EidosCallSignature::AddString_ON(const std::string &p_argume EidosCallSignature *EidosCallSignature::AddNumeric_ON(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskNumeric | kEidosValueMaskOptional | kEidosValueMaskNULL, p_argument_name, nullptr, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddLogicalEquiv_ON(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskLogicalEquiv | kEidosValueMaskOptional | kEidosValueMaskNULL, p_argument_name, nullptr, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddIntObject_ON(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskInt | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskNULL, p_argument_name, p_argument_class, std::move(p_default_value)); } +EidosCallSignature *EidosCallSignature::AddIntStringObject_ON(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskNULL, p_argument_name, p_argument_class, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddObject_ON(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskNULL, p_argument_name, p_argument_class, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddLogical_SN(const std::string &p_argument_name) { return AddArg(kEidosValueMaskLogical | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, nullptr); } @@ -274,6 +280,7 @@ EidosCallSignature *EidosCallSignature::AddString_SN(const std::string &p_argume EidosCallSignature *EidosCallSignature::AddNumeric_SN(const std::string &p_argument_name) { return AddArg(kEidosValueMaskNumeric | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, nullptr); } EidosCallSignature *EidosCallSignature::AddLogicalEquiv_SN(const std::string &p_argument_name) { return AddArg(kEidosValueMaskLogicalEquiv | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, nullptr); } EidosCallSignature *EidosCallSignature::AddIntObject_SN(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskInt | kEidosValueMaskObject | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, p_argument_class); } +EidosCallSignature *EidosCallSignature::AddIntStringObject_SN(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, p_argument_class); } EidosCallSignature *EidosCallSignature::AddObject_SN(const std::string &p_argument_name, const EidosClass *p_argument_class) { return AddArg(kEidosValueMaskObject | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, p_argument_class); } EidosCallSignature *EidosCallSignature::AddLogical_OSN(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskLogical | kEidosValueMaskOptional | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, nullptr, std::move(p_default_value)); } @@ -284,6 +291,7 @@ EidosCallSignature *EidosCallSignature::AddString_OSN(const std::string &p_argum EidosCallSignature *EidosCallSignature::AddNumeric_OSN(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskNumeric | kEidosValueMaskOptional | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, nullptr, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddLogicalEquiv_OSN(const std::string &p_argument_name, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskLogicalEquiv | kEidosValueMaskOptional | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, nullptr, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddIntObject_OSN(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskInt | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, p_argument_class, std::move(p_default_value)); } +EidosCallSignature *EidosCallSignature::AddIntStringObject_OSN(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, p_argument_class, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::AddObject_OSN(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value) { return AddArgWithDefault(kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton | kEidosValueMaskNULL, p_argument_name, p_argument_class, std::move(p_default_value)); } EidosCallSignature *EidosCallSignature::MarkDeprecated(void) diff --git a/eidos/eidos_call_signature.h b/eidos/eidos_call_signature.h index 5b8730b9..3f783945 100644 --- a/eidos/eidos_call_signature.h +++ b/eidos/eidos_call_signature.h @@ -89,6 +89,7 @@ class EidosCallSignature EidosCallSignature *AddIntString(const std::string &p_argument_name); EidosCallSignature *AddString(const std::string &p_argument_name); EidosCallSignature *AddIntObject(const std::string &p_argument_name, const EidosClass *p_argument_class); + EidosCallSignature *AddIntStringObject(const std::string &p_argument_name, const EidosClass *p_argument_class); EidosCallSignature *AddObject(const std::string &p_argument_name, const EidosClass *p_argument_class); EidosCallSignature *AddNumeric(const std::string &p_argument_name); EidosCallSignature *AddLogicalEquiv(const std::string &p_argument_name); @@ -102,6 +103,7 @@ class EidosCallSignature EidosCallSignature *AddIntString_O(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddString_O(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddIntObject_O(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); + EidosCallSignature *AddIntStringObject_O(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); EidosCallSignature *AddObject_O(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); EidosCallSignature *AddNumeric_O(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddLogicalEquiv_O(const std::string &p_argument_name, EidosValue_SP p_default_value); @@ -115,6 +117,7 @@ class EidosCallSignature EidosCallSignature *AddIntString_S(const std::string &p_argument_name); EidosCallSignature *AddString_S(const std::string &p_argument_name); EidosCallSignature *AddIntObject_S(const std::string &p_argument_name, const EidosClass *p_argument_class); + EidosCallSignature *AddIntStringObject_S(const std::string &p_argument_name, const EidosClass *p_argument_class); EidosCallSignature *AddObject_S(const std::string &p_argument_name, const EidosClass *p_argument_class); EidosCallSignature *AddNumeric_S(const std::string &p_argument_name); EidosCallSignature *AddLogicalEquiv_S(const std::string &p_argument_name); @@ -128,6 +131,7 @@ class EidosCallSignature EidosCallSignature *AddIntString_OS(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddString_OS(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddIntObject_OS(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); + EidosCallSignature *AddIntStringObject_OS(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); EidosCallSignature *AddObject_OS(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); EidosCallSignature *AddNumeric_OS(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddLogicalEquiv_OS(const std::string &p_argument_name, EidosValue_SP p_default_value); @@ -141,6 +145,7 @@ class EidosCallSignature EidosCallSignature *AddIntString_N(const std::string &p_argument_name); EidosCallSignature *AddString_N(const std::string &p_argument_name); EidosCallSignature *AddIntObject_N(const std::string &p_argument_name, const EidosClass *p_argument_class); + EidosCallSignature *AddIntStringObject_N(const std::string &p_argument_name, const EidosClass *p_argument_class); EidosCallSignature *AddObject_N(const std::string &p_argument_name, const EidosClass *p_argument_class); EidosCallSignature *AddNumeric_N(const std::string &p_argument_name); EidosCallSignature *AddLogicalEquiv_N(const std::string &p_argument_name); @@ -152,6 +157,7 @@ class EidosCallSignature EidosCallSignature *AddIntString_ON(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddString_ON(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddIntObject_ON(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); + EidosCallSignature *AddIntStringObject_ON(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); EidosCallSignature *AddObject_ON(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); EidosCallSignature *AddNumeric_ON(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddLogicalEquiv_ON(const std::string &p_argument_name, EidosValue_SP p_default_value); @@ -163,6 +169,7 @@ class EidosCallSignature EidosCallSignature *AddIntString_SN(const std::string &p_argument_name); EidosCallSignature *AddString_SN(const std::string &p_argument_name); EidosCallSignature *AddIntObject_SN(const std::string &p_argument_name, const EidosClass *p_argument_class); + EidosCallSignature *AddIntStringObject_SN(const std::string &p_argument_name, const EidosClass *p_argument_class); EidosCallSignature *AddObject_SN(const std::string &p_argument_name, const EidosClass *p_argument_class); EidosCallSignature *AddNumeric_SN(const std::string &p_argument_name); EidosCallSignature *AddLogicalEquiv_SN(const std::string &p_argument_name); @@ -174,6 +181,7 @@ class EidosCallSignature EidosCallSignature *AddIntString_OSN(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddString_OSN(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddIntObject_OSN(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); + EidosCallSignature *AddIntStringObject_OSN(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); EidosCallSignature *AddObject_OSN(const std::string &p_argument_name, const EidosClass *p_argument_class, EidosValue_SP p_default_value); EidosCallSignature *AddNumeric_OSN(const std::string &p_argument_name, EidosValue_SP p_default_value); EidosCallSignature *AddLogicalEquiv_OSN(const std::string &p_argument_name, EidosValue_SP p_default_value); From fff7c387e1ee41af060e92982b9a6e96c8ad1989 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 28 Dec 2025 14:22:12 -0600 Subject: [PATCH 045/107] add "independent dominance" to SLiM --- QtSLiM/help/SLiMHelpClasses.html | 60 ++-- QtSLiM/help/SLiMHelpFunctions.html | 2 +- SLiMgui/SLiMHelpClasses.rtf | 180 +++++++++--- SLiMgui/SLiMHelpFunctions.rtf | 14 +- VERSIONS | 9 + core/community.cpp | 14 +- core/haplosome.cpp | 19 +- core/individual.cpp | 1 + core/mutation.cpp | 444 ++++++++++++++++++++++------- core/mutation.h | 40 ++- core/mutation_type.cpp | 33 ++- core/mutation_type.h | 3 + core/polymorphism.cpp | 36 ++- core/population.cpp | 4 +- core/slim_globals.cpp | 2 + core/slim_globals.h | 4 + core/slim_test_genetics.cpp | 48 ++++ core/species.cpp | 7 +- core/species.h | 2 +- core/species_eidos.cpp | 4 + core/substitution.cpp | 215 ++++++++++++-- core/substitution.h | 19 +- 22 files changed, 928 insertions(+), 232 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 50b38bc9..e188a97c 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -251,7 +251,7 @@

The position index of the haplosome, within the set of haplosomes associated with the Chromosome object which this haplosome represents.  For example, if an individual in a multi-chromosome model has two haplosomes that represent a given chromosome within its haplosomes vector, the first of those haplosomes will have a chromosomeSubposition value of 0, the second will have a chromosomeSubposition value of 1.  For an intrinsically diploid chromosome in individuals generated by a standard biparental cross, the first haplosome (at subposition 0) came from its first parent (the female parent, in sexual models), and the second haplosome (at subposition 1) came from its second parent (the male parent, in sexual models).

haplosomePedigreeID => (integer$)

If pedigree tracking is turned on with initializeSLiMOptions(keepPedigrees=T) or tree-sequence recording is turned on with initializeTreeSeq(), haplosomePedigreeID is a “semi-unique” non-negative identifier for each haplosome in a simulation, never re-used throughout the duration of the simulation run.  The haplosomePedigreeID of a given haplosome will be equal to either (2*pedigreeID) or (2*pedigreeID + 1) of the individual that the haplosome belongs to (the former for a first haplosome of the individual, the latter for a second haplosome of the individual if one exists); this invariant relationship is guaranteed.

-

This value is “semi-unique” in the sense that it is shared by all of the first haplosomes of an individual, or by all of the second haplosomes of an individual.  In a single-chromosome model, a given individual will have just one first haplosome, and perhaps (depending on the chromosome type) one second haplosome, and so the value of haplosomePedigreeID for each of those haplosomes will be truly unique.  In a multi-chromosome model, however, an individual has a first haplosome for each chromosome, and perhaps (depending on the chromosome types) a second haplosome for each chromosome.  In that case, the value of haplosomePedigreeID is unique in the sense that it is different for each individual, but it is not unique in the sense that it will be shared by other haplosomes within the same individual – shared by all the first haplosomes, or shared by all the second haplosomes.  This “semi-uniqueness” is intentional; it allows haplosomePedigreeID to be used as a “key” that associates the haplosomes of an individual across disparate datasets, such as across the different tree sequences for each chromosome that are produced by tree-sequence recording in a multi-chromosome model.  See sections 1.5.1 and 8.3 for further discussion of multi-chromosome models.

+

This value is “semi-unique” in the sense that it is shared by all of the first haplosomes of an individual, or by all of the second haplosomes of an individual.  In a single-chromosome model, a given individual will have just one first haplosome, and perhaps (depending on the chromosome type) one second haplosome, and so the value of haplosomePedigreeID for each of those haplosomes will be truly unique.  In a multi-chromosome model, however, an individual has a first haplosome for each chromosome, and perhaps (depending on the chromosome types) a second haplosome for each chromosome.  In that case, the value of haplosomePedigreeID is unique in the sense that it is different for each individual, but it is not unique in the sense that it will be shared by other haplosomes within the same individual – shared by all the first haplosomes, or shared by all the second haplosomes.  This “semi-uniqueness” is intentional; it allows haplosomePedigreeID to be used as a “key” that associates the haplosomes of an individual across disparate datasets, such as across the different tree sequences for each chromosome that are produced by tree-sequence recording in a multi-chromosome model.

If neither pedigree tracking nor tree-sequence recording is enabled, this property is unavailable.

individual => (object<Individual>$)

The Individual object to which this haplosome belongs.

@@ -459,7 +459,7 @@

+ (void)demandPhenotype([Niso<Trait> trait = NULL], [logical$ forceRecalc = F])

Expresses “demand” for the phenotypes (trait values) of the trait(s) specified by trait, for the target vector of individuals.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  This triggers the recalculation of those trait values in those individuals, as needed.  More specifically, if forceRecalc is F (the default), the specified trait values will only be recalculated if their current value is NAN; if they have any other value, they are considered to already be calculated, and will not be recalculated.  If forceRecalc is T, the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date.  No value is returned by demandPhenotype(), since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.

This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set.  Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual.  The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.

-

Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so demandPhenotype() does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.  See sections 24.6 and 25.3 for details on fitness recalculation.

+

Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so demandPhenotype() does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.

– (object<Haplosome>)haplosomesForChromosomes([Niso<Chromosome> chromosomes = NULL], [Ni$ index = NULL], [logical$ includeNulls = T])

Returns a vector containing the haplosomes of the target individual that correspond to the chromosomes passed in chromosomes (following the order of the chromosomes property of Individual).  Chromosomes can be specified by id (integer), by symbol (string) or by the Chromosome objects themselves; if NULL is passed (the default), all chromosomes defined for the species are used, in the order in which they were defined.

For chromosomes that are intrinsically diploid (types "A", "X", and "Z", as well as the "H-" and "-Y" backward-compatibility chromosome types), index can be 0 or 1, requesting only the first or second haplosome, respectively, for that chromosome; for other chromosome types, index is ignored.  If includeNulls is T (the default), any null haplosomes corresponding to the specified chromosomes are included in the result; if it is F, null haplosomes are excluded.  See also the properties haplosomes, haplosomesNonNull, haploidGenome1, haploidGenome1NonNull, haploidGenome2, and haploidGenome2NonNull.

@@ -681,6 +681,7 @@

The Chromosome object with which the mutation is associated.

dominance => (float)

The dominance coefficient(s) of the mutation, taken from the default dominance coefficient(s) of its MutationType.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightDominance to access the dominance for that trait.  The dominance coefficient(s) of a mutation can be changed with the setDominanceForTrait() method.

+

If the target mutation has been configured to exhibit independent dominance by setting its dominance values to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this property does not provide that value of NAN; instead, it provides the dominance values that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance values.  These realized dominance values depend upon the mutation’s corresponding effects, and may change if those effects change.  The class Trait documentation provides further discussion of independent dominance.

Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s dominance coefficient to some number x, mut.dominance==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

effect => (float)

The effect size(s) of the mutation, drawn from the distribution of effect sizes of its MutationType.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightEffect to access the effect for that trait.  The effect size of a mutation can be changed with the setEffectForTrait() method.

@@ -692,6 +693,10 @@

The identifier for this mutation.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run.  These identifiers are not re-used during a run, except that if a population file is loaded from disk, the loaded mutations will receive their original identifier values as saved in the population file.

isFixed => (logical$)

T if the mutation has fixed (in the SLiM sense of having been converted to a Substitution object), F otherwise.  Since fixed/substituted mutations are removed from the simulation, you will only see this flag be T if you have held onto a mutation beyond its usual lifetime.

+

isIndependentDominance => (logical$)

+

T if the mutation is considered to exhibit independent dominance, F otherwise.  For a mutation to be considered to exhibit independent dominance, it must have received its dominance from a MutationType configured for independent dominance (with a default dominance of NAN), or had its dominance configured as NAN for all traits with setDominanceForTrait(); simply having the appropriate dominance value is not sufficient for this determination.  Its mutation type and effect are irrelevant to this determination.  A mutation can be T for both isIndependentDominance and isNeutral; in this case, the mutation is configured to exhibit independent dominance, but happens to currently have effects of 0.0 for all traits.  See the class Trait documentation in for further discussion of independent dominance.

+

isNeutral => (logical$)

+

T if the mutation is neutral, F otherwise.  For a mutation to be considered neutral, it must have an effect of exactly 0.0 for all traits; its mutation type and dominance are irrelevant to this determination.

isSegregating => (logical$)

T if the mutation is segregating (in the SLiM sense of not having been either lost or converted to a Substitution object), F otherwise.  Since both lost and fixed/substituted mutations are removed from the simulation, you will only see this flag be F if you have held onto a mutation beyond its usual lifetime.  Note that if isSegregating is F, isFixed will let you determine whether the mutation is no longer segregating because it was lost, or because it fixed.

mutationType => (object<MutationType>$)

@@ -712,13 +717,15 @@

5.10.2  Mutation methods

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

If the target mutation has been configured to exhibit independent dominance by setting its dominance values to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this method does not return that value of NAN; instead, it returns the dominance values that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance values.  These realized dominance values depend upon the mutation’s corresponding effects, and may change if those effects change.  The class Trait documentation provides further discussion of independent dominance.

– (float)effectForTrait([Niso<Trait> trait = NULL])

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+ (void)setDominanceForTrait([Niso<Trait> trait = NULL], [Nif dominance = NULL])

-

Sets the mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

Sets the target mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.)  In the second pattern, dominance is a singleton value; this sets the given dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the dominance for each of the specified traits in one mutation before moving to the next mutation.

+

A dominance value of NAN configures the mutation to use independent dominance; see the class Trait documentation for discussion of this feature, which can also be set at the MutationType level with initializeMutationType() or setDefaultDominanceForTrait().  A given mutation must be configured to exhibit independent dominance for all traits or for none; a mixed configuration is not allowed.

+ (void)setEffectForTrait([Niso<Trait> trait = NULL], [Nif effect = NULL])

Sets the mutation’s effect(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter effect must follow one of four patterns.  In the first pattern, effect is NULL; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation.  (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.)  In the second pattern, effect is a singleton value; this sets the given effect for each of the specified traits in each target mutation.  In the third pattern, effect is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation.  In the fourth pattern, effect is of length equal to the number of specified traits times the number of target mutations; this uses effect to provide a different effect value for each trait in each mutation, using consecutive values from effect to set the effect for each of the specified traits in one mutation before moving to the next mutation.

@@ -773,6 +780,7 @@

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

– (void)setDefaultDominanceForTrait(Niso<Trait> trait, float dominance)

Set the default dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of defaultDominance is used for each corresponding trait).

+

As for initializeMutationType(), a dominance value of NAN configures the mutation type to use “independent dominance” for new mutations of that type; see the class Trait documentation for discussion of independent dominance.  If the mutation type is configured to use independent dominance for one trait, it must use it for all traits; this is because the same restriction applies to mutations themselves.

– (void)setDefaultHemizygousDominanceForTrait(Niso<Trait> trait, float dominance)

Set the default hemizygous dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default hemizygous dominance for all specified traits), or must match the number of specified traits (in which case one element of dominance is used for each corresponding trait).

– (void)setEffectDistributionForTrait(Niso<Trait> trait, string$ distributionType, ...)

@@ -1319,7 +1327,7 @@

– (void)setCloningRate(numeric rate)

Set the cloning rate of this subpopulation.  The rate is changed to rate, which should be between 0.0 and 1.0, inclusive (see the SLiM manual for further details).  Clonal reproduction can be enabled in both non-sexual (i.e. hermaphroditic) and sexual simulations.  In non-sexual simulations, rate must be a singleton value representing the overall clonal reproduction rate for the subpopulation.  In sexual simulations, rate may be either a singleton (specifying the clonal reproduction rate for both sexes) or a vector containing two numeric values (the female and male cloning rates specified separately, at indices 0 and 1 respectively).  During mating and offspring generation, the probability that any given offspring individual will be generated by cloning – by asexual reproduction without gametes or meiosis – will be equal to the cloning rate (for its sex, in sexual simulations) set in the parental (not the offspring!) subpopulation.

– (void)setMigrationRates(io<Subpopulation> sourceSubpops, numeric rates)

-

Set the migration rates to this subpopulation from the subpopulations in sourceSubpops to the corresponding rates specified in rates; in other words, rates gives the expected fractions of the children in this subpopulation that will subsequently be generated from parents in the subpopulations sourceSubpops (see section 24.2.1).  The rates parameter may be a singleton value, in which case that rate is used for all subpopulations in sourceSubpops.  This method will only set the migration fractions from the subpopulations given; migration rates from other subpopulations will be left unchanged (explicitly set a zero rate to turn off migration from a given subpopulation).  The type of sourceSubpops may be either integer, specifying subpopulations by identifier, or object, specifying subpopulations directly.

+

Set the migration rates to this subpopulation from the subpopulations in sourceSubpops to the corresponding rates specified in rates; in other words, rates gives the expected fractions of the children in this subpopulation that will subsequently be generated from parents in the subpopulations sourceSubpops.  The rates parameter may be a singleton value, in which case that rate is used for all subpopulations in sourceSubpops.  This method will only set the migration fractions from the subpopulations given; migration rates from other subpopulations will be left unchanged (explicitly set a zero rate to turn off migration from a given subpopulation).  The type of sourceSubpops may be either integer, specifying subpopulations by identifier, or object, specifying subpopulations directly.

In general it is illegal to try to set the migration rate into a subpopulation from itself; that rate is, by definition, equal to the remainder after all migration from other subpopulations.  As a special case for convenience, it is legal to set a rate of 0.0 for all subpopulations in the species, including the target subpopulation.  For example, subpops.setMigrationRates(allSubpops, 0.0) will turn off all migration into the subpopulations in subpops.  The given rate of 0.0 from a subpop into itself is simply ignored, for this specific case only.

– (void)setSelfingRate(numeric$ rate)

Set the selfing rate of this subpopulation.  The rate is changed to rate, which should be between 0.0 and 1.0, inclusive (see the SLiM manual for further details).  Selfing can only be enabled in non-sexual (i.e. hermaphroditic) simulations.  During mating and offspring generation, the probability that any given offspring individual will be generated by selfing – by self-fertilization via gametes produced by meiosis by a single parent – will be equal to the selfing rate set in the parental (not the offspring!) subpopulation.

@@ -1347,29 +1355,33 @@

5.18  Class Substitution

5.18.1  Substitution properties

chromosome => (object<Chromosome>$)

-

The Chromosome object with which the mutation is associated.

+

The Chromosome object with which the substitution is associated.

dominance => (float)

-

The dominance coefficient(s) of the mutation, carried over from the original mutation object.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightDominance to access the dominance for that trait.

+

The dominance coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightDominance to access the dominance for that trait.

effect => (float)

-

The selection coefficient(s) of the mutation, carried over from the original mutation object.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightEffect to access the effect for that trait.

+

The selection coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightEffect to access the effect for that trait.

hemizygousDominance => (float)

-

The hemizygous dominance coefficient(s) of the mutation, carried over from the original mutation object.  In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the hemizygousDominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightHemizygousDominance to access the dominance for that trait.

-

id => (integer$)

-

The identifier for this mutation.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run, and that identifier is carried over to the Substitution object when the mutation fixes.

-

fixationTick => (integer$)

-

The tick in which this mutation fixed.

-

mutationType => (object<MutationType>$)

-

The MutationType from which this mutation was drawn.

-

nucleotide => (string$)

-

A string representing the nucleotide associated with this mutation; this will be "A", "C", "G", or "T".  If the mutation is not nucleotide-based, this property is unavailable.

-

nucleotideValue => (integer$)

-

An integer representing the nucleotide associated with this mutation; this will be 0 (A), 1 (C), 2 (G), or 3 (T).  If the mutation is not nucleotide-based, this property is unavailable.

-

originTick => (integer$)

-

The tick in which this mutation arose.

-

position => (integer$)

-

The position in the chromosome of this mutation.

-

subpopID <–> (integer$)

-

The identifier of the subpopulation in which this mutation arose.  This value is carried over from the Mutation object directly; if a “tag” value was used in the Mutation object, that value will carry over to the corresponding Substitution object.  The subpopID in Substitution is a read-write property to allow it to be used as a “tag” in the same way, if the origin subpopulation identifier is not needed.

+

The hemizygous dominance coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the hemizygousDominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightHemizygousDominance to access the dominance for that trait.

+

id => (integer$)

+

The identifier for this substitution.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run, and that identifier is carried over to the Substitution object when the mutation fixes.

+

isIndependentDominance => (logical$)

+

T if the substitution is considered to exhibit independent dominance, F otherwise.  The value of this property is carried over from the original mutation; see the same property on Mutation for further details.

+

isNeutral => (logical$)

+

T if the substitution is neutral, F otherwise.  The value of this property is carried over from the original mutation; see the same property on Mutation for further details.

+

fixationTick => (integer$)

+

The tick in which this substitution fixed.

+

mutationType => (object<MutationType>$)

+

The MutationType to which this substitution belongs.  The value of this property is carried over from the original mutation.

+

nucleotide => (string$)

+

A string representing the nucleotide associated with this substitution; this will be "A", "C", "G", or "T".  If the substitution is not nucleotide-based, this property is unavailable.

+

nucleotideValue => (integer$)

+

An integer representing the nucleotide associated with this substitution; this will be 0 (A), 1 (C), 2 (G), or 3 (T).  If the substitution is not nucleotide-based, this property is unavailable.

+

originTick => (integer$)

+

The tick in which the original mutation for this substitution arose.

+

position => (integer$)

+

The position in the chromosome of this substitution.

+

subpopID <–> (integer$)

+

The identifier of the subpopulation in which the original mutation for this substitution arose.  This value is carried over from the Mutation object directly; if a “tag” value was used in the Mutation object, that value will carry over to the corresponding Substitution object.  The subpopID in Substitution is a read-write property to allow it to be used as a “tag” in the same way, if the origin subpopulation identifier is not needed.

tag <–> (integer$)

A user-defined integer value.  The value of tag is carried over automatically from the original Mutation object.  Apart from that, the value of tag is not used by SLiM; it is free for you to use.

5.18.2  Substitution methods

diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 35b1de6a..5be47898 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -105,7 +105,7 @@

This function is written in Eidos, and its source code can be viewed with functionSource(), so you can copy and modify its code if you need to modify its functionality.

(object<MutationType>$)initializeMutationType(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

Add a mutation type at initialization time.  The id must not already be used for any mutation type in the simulation.  The id parameter may be either an integer giving the ID of the new mutation type, or a string giving the name of the new mutation type (such as "m5" to specify an ID of 5).  The global symbol for the new mutation type, such as m5, is immediately available; the return value also provides the new object.

-

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but the setDefaultHemizygousDominanceForTrait() method can configure it subsequently if desired.

+

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but the setDefaultHemizygousDominanceForTrait() method can configure it subsequently if desired.  A dominanceCoeff value of NAN configures the mutation type to use “independent dominance” for new mutations of that type; see the class Trait documentation for discussion of independent dominance.

The distributionType and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits.  The DES for the mutation type for a specific trait can subsequently be separately configured with the setEffectDistributionForTrait() method if desired.  The distributionType parameter may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for an exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  If distributionType is NULL (the default), a fixed effect of 0.0 is used, representing a neutral DES; this is equivalent to type "f" except that the value 0.0 is assumed and must not be supplied.  See the MutationType class documentation for discussion of the various DESs and their uses.

Note that by default in WF models, all mutations of a given mutation type will be converted into Substitution objects when they reach fixation, for efficiency reasons.  If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the convertToSubstitution property of MutationType to F.  In contrast, by default in nonWF models mutations will not be converted into Substitution objects when they reach fixation; convertToSubstitution is F by default in nonWF models.  To enable conversion in nonWF models for neutral mutation types with no indirect fitness effects, you should therefore set convertToSubstitution to T.

(object<MutationType>$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 8602029a..cc96853a 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -1623,7 +1623,7 @@ This value is \'93semi-unique\'94 in the sense that it is shared by \f1\i not \f4\i0 unique in the sense that it will be shared by other haplosomes within the same individual \'96 shared by all the first haplosomes, or shared by all the second haplosomes. This \'93semi-uniqueness\'94 is intentional; it allows \f3\fs18 haplosomePedigreeID -\f4\fs20 to be used as a \'93key\'94 that associates the haplosomes of an individual across disparate datasets, such as across the different tree sequences for each chromosome that are produced by tree-sequence recording in a multi-chromosome model. See sections 1.5.1 and 8.3 for further discussion of multi-chromosome models.\ +\f4\fs20 to be used as a \'93key\'94 that associates the haplosomes of an individual across disparate datasets, such as across the different tree sequences for each chromosome that are produced by tree-sequence recording in a multi-chromosome model.\ If neither pedigree tracking nor tree-sequence recording is enabled, this property is unavailable.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -3674,11 +3674,10 @@ See the description of the \f4\fs20 , the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date. No value is returned by \f3\fs18 demandPhenotype() \f4\fs20 , since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set. Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual. The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.\ +This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set. Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual. The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.\ Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so \f3\fs18 demandPhenotype() -\f4\fs20 does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way. See sections 24.6 and 25.3 for details on fitness recalculation.\ +\f4\fs20 does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(object)haplosomesForChromosomes([Niso\'a0chromosomes\'a0=\'a0NULL], [Ni$\'a0index\'a0=\'a0NULL], [logical$\'a0includeNulls\'a0=\'a0T])\ @@ -6058,7 +6057,20 @@ You can get the \f4\fs20 to access the dominance for that trait. The dominance coefficient(s) of a mutation can be changed with the \f3\fs18 setDominanceForTrait() \f4\fs20 method.\ -Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 If the target mutation has been configured to exhibit independent dominance by setting its dominance values to +\f3\fs18 NAN +\f4\fs20 , as discussed in +\f3\fs18 initializeMutationType() +\f4\fs20 and +\f3\fs18 setDominanceForTrait() +\f4\fs20 , this property does not provide that value of +\f3\fs18 NAN +\f4\fs20 ; instead, it provides the dominance values that will actually be used by SLiM to implement independent dominance, referred to as the \'93realized\'94 dominance values. These realized dominance values depend upon the mutation\'92s corresponding effects, and may change if those effects change. The class +\f3\fs18 Trait +\f4\fs20 documentation provides further discussion of independent dominance.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s dominance coefficient to some number \f3\fs18 x @@ -6142,6 +6154,42 @@ Note that dominance coefficients in SLiM have a quirk: they are stored internall \f4\fs20 if you have held onto a mutation beyond its usual lifetime.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 isIndependentDominance => (logical$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 T +\f4\fs20 if the mutation is considered to exhibit independent dominance, +\f3\fs18 F +\f4\fs20 otherwise. For a mutation to be considered to exhibit independent dominance, it must have received its dominance from a +\f3\fs18 MutationType +\f4\fs20 configured for independent dominance (with a default dominance of +\f3\fs18 NAN +\f4\fs20 ), or had its dominance configured as +\f3\fs18 NAN +\f4\fs20 for all traits with +\f3\fs18 setDominanceForTrait() +\f4\fs20 ; simply having the appropriate dominance value is not sufficient for this determination. Its mutation type and effect are irrelevant to this determination. A mutation can be +\f3\fs18 T +\f4\fs20 for both +\f3\fs18 isIndependentDominance +\f4\fs20 and +\f3\fs18 isNeutral +\f4\fs20 ; in this case, the mutation is configured to exhibit independent dominance, but happens to currently have effects of +\f3\fs18 0.0 +\f4\fs20 for all traits. See the class +\f3\fs18 Trait +\f4\fs20 documentation in for further discussion of independent dominance.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 isNeutral => (logical$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 T +\f4\fs20 if the mutation is neutral, +\f3\fs18 F +\f4\fs20 otherwise. For a mutation to be considered neutral, it must have an effect of exactly +\f3\fs18 0.0 +\f4\fs20 for all traits; its mutation type and dominance are irrelevant to this determination.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 isSegregating => (logical$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf2 T @@ -6269,6 +6317,18 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait \f4\fs20 .\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 If the target mutation has been configured to exhibit independent dominance by setting its dominance values to +\f3\fs18 NAN +\f4\fs20 , as discussed in +\f3\fs18 initializeMutationType() +\f4\fs20 and +\f3\fs18 setDominanceForTrait() +\f4\fs20 , this method does not return that value of +\f3\fs18 NAN +\f4\fs20 ; instead, it returns the dominance values that will actually be used by SLiM to implement independent dominance, referred to as the \'93realized\'94 dominance values. These realized dominance values depend upon the mutation\'92s corresponding effects, and may change if those effects change. The class +\f3\fs18 Trait +\f4\fs20 documentation provides further discussion of independent dominance.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Niso\'a0trait\'a0=\'a0NULL])\ @@ -6317,7 +6377,7 @@ If you don\'92t care which subpopulation a mutation originated in, the \f3\fs18 \cf2 +\'a0(void)setDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 Sets the mutation\'92s dominance coefficient(s) for the trait(s) specified by +\f4\fs20 \cf2 Sets the target mutation\'92s dominance coefficient(s) for the trait(s) specified by \f3\fs18 trait \f4\fs20 . The traits can be specified as \f3\fs18 integer @@ -6345,6 +6405,17 @@ The parameter \f4\fs20 to provide a different dominance coefficient for each trait in each mutation, using consecutive values from \f3\fs18 dominance \f4\fs20 to set the dominance for each of the specified traits in one mutation before moving to the next mutation.\ +A dominance value of +\f3\fs18 NAN +\f4\fs20 configures the mutation to use independent dominance; see the class +\f3\fs18 Trait +\f4\fs20 documentation for discussion of this feature, which can also be set at the +\f3\fs18 MutationType +\f4\fs20 level with +\f3\fs18 initializeMutationType() +\f4\fs20 or +\f3\fs18 setDefaultDominanceForTrait() +\f4\fs20 . A given mutation must be configured to exhibit independent dominance for all traits or for none; a mixed configuration is not allowed.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 +\'a0(void)setEffectForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0effect\'a0=\'a0NULL])\ @@ -6784,6 +6855,17 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of \f3\fs18 defaultDominance \f4\fs20 is used for each corresponding trait).\ +As for +\f3\fs18 initializeMutationType() +\f4\fs20 , a +\f3\fs18 dominance +\f4\fs20 value of +\f3\fs18 NAN +\f4\fs20 configures the mutation type to use \'93independent dominance\'94 for new mutations of that type; see the class +\f3\fs18 Trait +\f4\fs20 documentation for discussion of independent dominance. If the mutation type is configured to use independent dominance for one trait, it must use it for +\f1\i all +\f4\i0 traits; this is because the same restriction applies to mutations themselves.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(void)setDefaultHemizygousDominanceForTrait(Niso\'a0trait, float\'a0dominance)\ @@ -13755,7 +13837,7 @@ This method is similar to getting the \f3\fs18 rates \f4\fs20 gives the expected fractions of the children in this subpopulation that will subsequently be generated from parents in the subpopulations \f3\fs18 sourceSubpops -\f4\fs20 (see section 24.2.1). The +\f4\fs20 . The \f3\fs18 rates \f4\fs20 parameter may be a singleton value, in which case that rate is used for all subpopulations in \f3\fs18 sourceSubpops @@ -14081,13 +14163,13 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 \cf2 The \f3\fs18 Chromosome -\f4\fs20 object with which the mutation is associated.\ +\f4\fs20 object with which the substitution is associated.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 dominance => (float)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The dominance coefficient(s) of the mutation, carried over from the original mutation object. In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f4\fs20 \cf2 The dominance coefficient(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the \f3\fs18 dominanceForTrait() \f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named \f3\fs18 height @@ -14101,7 +14183,7 @@ Note that this method is only for use in nonWF models, in which migration is man \f3\fs18 \cf2 effect => (float)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The selection coefficient(s) of the mutation, carried over from the original mutation object. In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the +\f4\fs20 \cf2 The selection coefficient(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the \f3\fs18 effectForTrait() \f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named \f3\fs18 height @@ -14115,7 +14197,7 @@ Note that this method is only for use in nonWF models, in which migration is man \f3\fs18 \cf2 hemizygousDominance => (float)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The hemizygous dominance coefficient(s) of the mutation, carried over from the original mutation object. In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f4\fs20 \cf2 The hemizygous dominance coefficient(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the \f3\fs18 hemizygousDominanceForTrait() \f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named \f3\fs18 height @@ -14126,38 +14208,54 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 to access the dominance for that trait.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 id => (integer$)\ +\f3\fs18 \cf2 id => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The identifier for this mutation. Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run, and that identifier is carried over to the +\f4\fs20 \cf2 The identifier for this substitution. Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run, and that identifier is carried over to the \f3\fs18 Substitution -\f4\fs20 object when the mutation fixes. -\f5 \ +\f4\fs20 object when the mutation fixes.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 fixationTick => (integer$)\ +\f3\fs18 \cf2 isIndependentDominance => (logical$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 T +\f4\fs20 if the substitution is considered to exhibit independent dominance, +\f3\fs18 F +\f4\fs20 otherwise. The value of this property is carried over from the original mutation; see the same property on +\f3\fs18 Mutation +\f4\fs20 for further details.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f4\fs20 \cf0 The tick in which this mutation fixed. -\f5 \ +\f3\fs18 \cf2 isNeutral => (logical$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 T +\f4\fs20 if the substitution is neutral, +\f3\fs18 F +\f4\fs20 otherwise. The value of this property is carried over from the original mutation; see the same property on +\f3\fs18 Mutation +\f4\fs20 for further details.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 mutationType => (object$)\ +\f3\fs18 \cf2 fixationTick => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The -\f3\fs18 MutationType -\f4\fs20 from which this mutation was drawn. -\f5 \ +\f4\fs20 \cf2 The tick in which this substitution fixed.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \expnd0\expndtw0\kerning0 -nucleotide => (string$)\ +\f3\fs18 \cf2 mutationType => (object$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 A +\f4\fs20 \cf2 The +\f3\fs18 MutationType +\f4\fs20 to which this substitution belongs. The value of this property is carried over from the original mutation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 nucleotide => (string$) +\f4\fs20 \ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 A \f3\fs18 string -\f4\fs20 representing the nucleotide associated with this mutation; this will be +\f4\fs20 representing the nucleotide associated with this substitution; this will be \f3\fs18 "A" \f4\fs20 , \f3\fs18 "C" @@ -14165,15 +14263,15 @@ nucleotide => (string$)\ \f3\fs18 "G" \f4\fs20 , or \f3\fs18 "T" -\f4\fs20 . If the mutation is not nucleotide-based, this property is unavailable.\ +\f4\fs20 . If the substitution is not nucleotide-based, this property is unavailable.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 nucleotideValue => (integer$)\ +\f3\fs18 \cf2 nucleotideValue => (integer$) +\f4\fs20 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf2 An +\cf2 An \f3\fs18 integer -\f4\fs20 representing the nucleotide associated with this mutation; this will be +\f4\fs20 representing the nucleotide associated with this substitution; this will be \f3\fs18 0 \f4\fs20 (A), \f3\fs18 1 @@ -14181,27 +14279,25 @@ nucleotide => (string$)\ \f3\fs18 2 \f4\fs20 (G), or \f3\fs18 3 -\f4\fs20 (T). If the mutation is not nucleotide-based, this property is unavailable.\ +\f4\fs20 (T). If the substitution is not nucleotide-based, this property is unavailable.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 \kerning1\expnd0\expndtw0 originTick => (integer$)\ +\f3\fs18 \cf2 originTick => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The tick in which this mutation arose. -\f5 \ +\f4\fs20 \cf2 The tick in which the original mutation for this substitution arose.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 position => (integer$)\ +\f3\fs18 \cf2 position => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The position in the chromosome of this mutation. -\f5 \ +\f4\fs20 \cf2 The position in the chromosome of this substitution.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 subpopID <\'96> (integer$)\ +\f3\fs18 \cf2 subpopID <\'96> (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The identifier of the subpopulation in which this mutation arose. This value is carried over from the +\f4\fs20 \cf2 The identifier of the subpopulation in which the original mutation for this substitution arose. This value is carried over from the \f3\fs18 Mutation \f4\fs20 object directly; if a \'93tag\'94 value was used in the \f3\fs18 Mutation @@ -14211,7 +14307,7 @@ nucleotide => (string$)\ \f3\fs18 subpopID \f4\fs20 in \f3\fs18 Substitution -\f4\fs20 is a read-write property to allow it to be used as a \'93tag\'94 in the same way, if the origin subpopulation identifier is not needed.\ +\f4\fs20 is a read-write property to allow it to be used as a \'93tag\'94 in the same way, if the origin subpopulation identifier is not needed.\cf0 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 tag <\'96> (integer$)\ diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 4a783a70..9ba9e8a1 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -901,7 +901,13 @@ The \f1\fs18 1.0 \f2\fs20 , but the \f1\fs18 setDefaultHemizygousDominanceForTrait() -\f2\fs20 method can configure it subsequently if desired.\ +\f2\fs20 method can configure it subsequently if desired. A +\f1\fs18 dominanceCoeff +\f2\fs20 value of +\f1\fs18 NAN +\f2\fs20 configures the mutation type to use \'93independent dominance\'94 for new mutations of that type; see the class +\f1\fs18 Trait +\f2\fs20 documentation for discussion of independent dominance.\ The \f1\fs18 distributionType \f2\fs20 and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits. The DES for the mutation type for a specific trait can subsequently be separately configured with the @@ -1496,8 +1502,7 @@ The \f2\fs20 object is returned. For more details on the way that traits work in SLiM, beyond what is given below, see the \f1\fs18 Trait \f2\fs20 class documentation.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 The +The \f1\fs18 name \f2\fs20 parameter gives the name of the new trait. This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the \f1\fs18 Individual @@ -1534,8 +1539,7 @@ The \f2\fs20 object to be referenced simply as \f1\fs18 height \f2\fs20 .\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 The +The \f1\fs18 type \f2\fs20 parameter gives the type of trait to be created, as a \f1\fs18 string diff --git a/VERSIONS b/VERSIONS index fa3b4b47..4758b666 100644 --- a/VERSIONS +++ b/VERSIONS @@ -112,6 +112,15 @@ multitrait branch: extend initializeMutationType() and initializeMutationTypeNuc() to make the effect distribution optional with [Ns$ distributionType = NULL], where NULL is equivalent to `"f", 0.0` make setOffsetForTrait() clamp values to 0.0 for multiplicative traits; fix doc to discuss clamping of effects extend all methods taking a `Nio trait` parameter to `Niso trait`, allowing traits to also be identified by string name + add the concept of "independent dominance" to SLiM; mutations with independent dominance allow optimization because two heterozygous effects equals one homozygous effect + initializeMutationType() and initializeMutationTypeNuc() now allow NAN to be passed for dominanceCoeff, indicating independent dominance for that mutation type (for all traits) + new-mutation construction now sets the `is_independent_dominance_` flag for mutations from such a mutation type + add isIndependentDominance and isNeutral properties to Mutation and Substitution + MutationType's setDefaultDominanceForTrait() now correctly handles converting mutation types to and from a default of independent dominance + Mutation's setDominanceForTrait() method now correctly handles converting mutations to and from independent dominance + a new RealizedDominance() internal (C++) method calculates the correct dominance value to use when independent dominance is configured for a mutation + Mutation and Substitution's dominanceForTrait() methods now use RealizedDominance(), and thus never report NAN as a dominance value (use isIndependentDominance to check for that) + note that there is no concept of independent dominance for the hemizygous dominance coefficient, since hemizygous mutations are present in only one copy by definition version 5.1 (Eidos version 4.1): diff --git a/core/community.cpp b/core/community.cpp index e7094415..273dfbc9 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -2571,16 +2571,17 @@ void Community::AllSpecies_CheckIntegrity(void) return; #endif - // Check the integrity of the mutation registry; all MutationIndex values should be in range for (Species *species : all_species_) { + // Check the integrity of the mutation registry; all MutationIndex values should be in range int registry_size; const MutationIndex *registry = species->population_.MutationRegistry(®istry_size); std::vector indices; if (registry_size) { - MutationIndex mutBlockCapacity = species->SpeciesMutationBlock()->capacity_; + MutationBlock *mutationBlock = species->SpeciesMutationBlock(); + MutationIndex mutBlockCapacity = mutationBlock->capacity_; for (int registry_index = 0; registry_index < registry_size; ++registry_index) { @@ -2590,6 +2591,11 @@ void Community::AllSpecies_CheckIntegrity(void) EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) mutation index " << mutation_index << " out of the mutation block." << EidosTerminate(); indices.push_back(mutation_index); + + // check mutation integrity + Mutation *mut = mutationBlock->MutationForIndex(mutation_index); + + mut->SelfConsistencyCheck(" in AllSpecies_CheckIntegrity()"); } size_t original_size = indices.size(); @@ -2600,6 +2606,10 @@ void Community::AllSpecies_CheckIntegrity(void) if (indices.size() != original_size) EIDOS_TERMINATION << "ERROR (Community::AllSpecies_CheckIntegrity): (internal error) duplicate mutation index in the mutation registry (size difference " << (original_size - indices.size()) << ")." << EidosTerminate(); } + + // Check the integrity of all substitution objects + for (Substitution *sub : species->population_.substitutions_) + sub->SelfConsistencyCheck(" in AllSpecies_CheckIntegrity()"); } #endif } diff --git a/core/haplosome.cpp b/core/haplosome.cpp index b1e7f723..e3d67b56 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -1995,7 +1995,12 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i const Mutation *mut = polymorphism->mutation_ptr_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); - p_out << mut_trait_info[0].dominance_coeff_; + slim_effect_t dominance = mut_trait_info[0].dominance_coeff_UNSAFE_; // can be NAN + + if (std::isnan(dominance)) + p_out << "NAN"; + else + p_out << dominance; } p_out << ";"; @@ -2123,7 +2128,14 @@ void Haplosome::_PrintVCF(std::ostream &p_out, const Haplosome **p_haplosomes, i p_out << "MID=" << mutation->mutation_id_ << ";"; p_out << "S=" << mut_trait_info->effect_size_ << ";"; - p_out << "DOM=" << mut_trait_info->dominance_coeff_ << ";"; + + slim_effect_t dominance = mut_trait_info->dominance_coeff_UNSAFE_; // can be NAN + + if (std::isnan(dominance)) + p_out << "DOM=" << "NAN" << ";"; + else + p_out << "DOM=" << dominance << ";"; + p_out << "PO=" << mutation->subpop_index_ << ";"; p_out << "TO=" << mutation->origin_tick_ << ";"; p_out << "MT=" << mutation->mutation_type_ptr_->mutation_type_id_ << ";"; @@ -2957,6 +2969,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID } // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... and hemizygous dominance... + // FIXME MULTITRAIT this code will also now need to handle the independent dominance case new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ @@ -3459,6 +3472,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... and hemizygous dominance... + // FIXME MULTITRAIT this code will also now need to handle the independent dominance case Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ @@ -4019,6 +4033,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt dominance_coeff = info_domcoeffs[alt_allele_index]; else dominance_coeff = mutation_type_ptr->DefaultDominanceForTrait(0); // FIXME MULTITRAIT; also think about hemizygous dominance + // FIXME MULTITRAIT this code will also now need to handle the independent dominance case, for which NaN should be in the metadata // get the selection coefficient from S, or draw one from the mutation type slim_effect_t selection_coeff; diff --git a/core/individual.cpp b/core/individual.cpp index e5414b2a..eac7cc21 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -5284,6 +5284,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal dominance_coeff = info_domcoeffs[alt_allele_index]; else dominance_coeff = mutation_type_ptr->DefaultDominanceForTrait(0); // FIXME MULTITRAIT; also think about hemizygous dominance + // FIXME MULTITRAIT this code will also now need to handle the independent dominance case, for which NaN should be in the metadata // get the selection coefficient from S, or draw one from the mutation type slim_effect_t selection_coeff; diff --git a/core/mutation.cpp b/core/mutation.cpp index 4af9ac55..6d30110c 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -24,6 +24,7 @@ #include "community.h" #include "species.h" #include "mutation_block.h" +#include "trait.h" #include #include @@ -64,7 +65,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. - is_neutral_ = true; // will be set to false below as needed + is_neutral_ = true; // will be set to false below as needed + + // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits + is_independent_dominance_ = std::isnan(p_dominance_coeff); for (int trait_index = 0; trait_index < trait_count; ++trait_index) { @@ -73,12 +77,13 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ TraitType traitType = trait->Type(); // FIXME MULTITRAIT: This constructor needs to change to have a whole vector of trait information passed in, for effect and dominance - slim_effect_t effect = trait_index ? p_selection_coeff : 0.0; - slim_effect_t dominance = trait_index ? p_dominance_coeff : 0.5; + // for now we use the values passed in for trait 0, and make other traits neutral + slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : 0.0; + slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : 0.5; // can be NAN slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably traitInfoRec->effect_size_ = effect; - traitInfoRec->dominance_coeff_ = dominance; + traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; if (effect != 0.0) @@ -89,16 +94,19 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! + // get the realized dominance to handle the possibility of independent dominance + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + if (traitType == TraitType::kMultiplicative) { traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect); traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); } else // (traitType == TraitType::kAdditive) { traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect); traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); } } @@ -119,6 +127,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ } } +#if DEBUG + SelfConsistencyCheck(" in Mutation::Mutation()"); +#endif + #if DEBUG_MUTATIONS std::cout << "Mutation constructed: " << this << std::endl; #endif @@ -153,7 +165,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. - is_neutral_ = true; // will be set to false below as needed + is_neutral_ = true; // will be set to false below as needed + + // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits + is_independent_dominance_ = std::isnan(mutation_type_ptr_->DefaultDominanceForTrait(0)); if (mutation_type_ptr_->all_pure_neutral_DES_) { @@ -166,7 +181,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ TraitType traitType = trait->Type(); traitInfoRec->effect_size_ = 0.0; - traitInfoRec->dominance_coeff_ = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); + traitInfoRec->dominance_coeff_UNSAFE_ = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); if (traitType == TraitType::kMultiplicative) @@ -187,7 +202,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ { // The DES of the mutation type is not pure neutral. Note that species.pure_neutral_ might still be true // at this point; the mutation type for this mutation might not be used by any genomic element type, - // because we might be getting called by addNewDrawn() mutation for a type that is otherwise unused. + // because we might be getting called by addNewDrawnMutation() for a type that is otherwise unused. for (int trait_index = 0; trait_index < trait_count; ++trait_index) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; @@ -195,11 +210,11 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ TraitType traitType = trait->Type(); slim_effect_t effect = mutation_type_ptr_->DrawEffectForTrait(trait_index); - slim_effect_t dominance = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); + slim_effect_t dominance = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); // can be NAN slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); traitInfoRec->effect_size_ = effect; - traitInfoRec->dominance_coeff_ = dominance; + traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; if (effect != 0.0) @@ -209,16 +224,19 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! + // get the realized dominance to handle the possibility of independent dominance + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + if (traitType == TraitType::kMultiplicative) { traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect); traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); } else // (traitType == TraitType::kAdditive) { traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect); traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); } } @@ -240,6 +258,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ } } +#if DEBUG + SelfConsistencyCheck(" in Mutation::Mutation()"); +#endif + #if DEBUG_MUTATIONS std::cout << "Mutation constructed: " << this << std::endl; #endif @@ -254,6 +276,7 @@ Mutation::Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_typ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_subpop_index), origin_tick_(p_tick), chromosome_index_(p_chromosome_index), state_(MutationState::kNewMutation), nucleotide_(p_nucleotide), mutation_id_(p_mutation_id) { Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); MutationBlock *mutation_block = species.SpeciesMutationBlock(); // initialize the tag to the "unset" value @@ -264,26 +287,30 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ mutation_block->refcount_buffer_[mutation_index] = 0; int trait_count = mutation_block->trait_count_; - MutationTraitInfo *mut_trait_info = mutation_block->trait_info_buffer_ + trait_count * mutation_index; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. - is_neutral_ = true; // will be set to false by EffectChanged() as needed + is_neutral_ = true; // will be set to false below as needed + + // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits + is_independent_dominance_ = std::isnan(p_dominance_coeff); for (int trait_index = 0; trait_index < trait_count; ++trait_index) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - Trait *trait = species.Traits()[trait_index]; + Trait *trait = traits[trait_index]; TraitType traitType = trait->Type(); - // FIXME MULTITRAIT: The per-trait info will soon supplant selection_coeff_ and dominance_coeff_; this initialization code needs to be fleshed out + // FIXME MULTITRAIT: This constructor needs to change to have a whole vector of trait information passed in, for effect and dominance + // for now we use the values passed in for trait 0, and make other traits neutral slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : 0.0; - slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : 0.5; - slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); + slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : 0.5; // can be NAN + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably traitInfoRec->effect_size_ = effect; - traitInfoRec->dominance_coeff_ = dominance; + traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; if (effect != 0.0) @@ -294,16 +321,19 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! + // get the realized dominance to handle the possibility of independent dominance + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + if (traitType == TraitType::kMultiplicative) { traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect); traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); } else // (traitType == TraitType::kAdditive) { traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect); traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); } } @@ -324,6 +354,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ } } +#if DEBUG + SelfConsistencyCheck(" in Mutation::Mutation()"); +#endif + #if DEBUG_MUTATIONS std::cout << "Mutation constructed: " << this << std::endl; #endif @@ -337,12 +371,116 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ gSLiM_next_mutation_id = mutation_id_ + 1; } +void Mutation::SelfConsistencyCheck(const std::string &p_message_end) +{ + if (!mutation_type_ptr_) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) mutation_type_ptr_ is nullptr" << p_message_end << "." << EidosTerminate(); + + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + + if (!mut_trait_info) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) mut_trait_info is nullptr" << p_message_end << "." << EidosTerminate(); + + const std::vector &traits = species.Traits(); + int trait_count = (int)traits.size(); + bool all_neutral_effects = true; + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + Trait *trait = traits[trait_index]; + MutationTraitInfo &traitInfoRec = mut_trait_info[trait_index]; + + if (!std::isfinite(traitInfoRec.effect_size_)) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation effect size is non-finite" << p_message_end << "." << EidosTerminate(); + if (std::isinf(traitInfoRec.dominance_coeff_UNSAFE_)) // NAN is legal sometimes, checked below + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation dominance is infinite" << p_message_end << "." << EidosTerminate(); + if (!std::isfinite(traitInfoRec.hemizygous_dominance_coeff_)) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation hemizygous dominance is non-finite" << p_message_end << "." << EidosTerminate(); + + if ((is_independent_dominance_ && !std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) || + (!is_independent_dominance_ && std::isnan(traitInfoRec.dominance_coeff_UNSAFE_))) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation independent dominance state is inconsistent" << p_message_end << "." << EidosTerminate(); + + slim_effect_t effect_size = traitInfoRec.effect_size_; + slim_effect_t dominance = RealizedDominanceForTrait(trait); // handle NAN for independent dominance + slim_effect_t hemizygous_dominance = traitInfoRec.hemizygous_dominance_coeff_; + slim_effect_t correct_homozygous_effect, correct_heterozygous_effect, correct_hemizygous_effect; + + if (trait->Type() == TraitType::kAdditive) + { + correct_homozygous_effect = (slim_effect_t)(2.0f * effect_size); // 2a + correct_heterozygous_effect = (slim_effect_t)(2.0f * dominance * effect_size); // 2ha + correct_hemizygous_effect = (slim_effect_t)(2.0f * hemizygous_dominance * effect_size); // 2ha (using h_hemi) + } + else + { + correct_homozygous_effect = (slim_effect_t)std::max(0.0f, 1.0f + effect_size); // 1 + s + correct_heterozygous_effect = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect_size); // 1 + hs + correct_hemizygous_effect = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect_size); // 1 + hs (using h_hemi) + } + + if (correct_homozygous_effect != traitInfoRec.homozygous_effect_) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) homozygous_effect_ does not match expectations" << p_message_end << "." << EidosTerminate(); + if (correct_heterozygous_effect != traitInfoRec.heterozygous_effect_) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) heterozygous_effect_ does not match expectations" << p_message_end << "." << EidosTerminate(); + if (correct_hemizygous_effect != traitInfoRec.hemizygous_effect_) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) hemizygous_effect_ does not match expectations" << p_message_end << "." << EidosTerminate(); + + if (traitInfoRec.effect_size_ != 0.0) + all_neutral_effects = false; + } + + if ((is_neutral_ && !all_neutral_effects) || (!is_neutral_ && all_neutral_effects)) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation neutrality state is inconsistent" << p_message_end << "." << EidosTerminate(); +} + +slim_effect_t Mutation::RealizedDominanceForTrait(Trait *p_trait) +{ + int64_t trait_index = p_trait->Index(); + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + MutationTraitInfo &traitInfoRec = mut_trait_info[trait_index]; + + if (std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) + { + // NAN indicates independent dominance and needs to be handled specially here + if (p_trait->Type() == TraitType::kAdditive) + { + // for additive traits independent dominance is always 0.5 + return 0.5; + } + else + { + // for multiplicative traits the dominance is calculated as (sqrt(1+s)-1)/s, except that the effect + // is clamped to a minimum of -1.0 to avoid a negative square root; this is correct, since it means + // that 1+s (1 + -1 == 0) equals 2(1+hs): (2 x (1 + 1 x -1)) == (2 x 0) == 0. If the resulting + // dominance of 1.0 is used in 1+hs with the unclipped effect size, a negative mutational effect will + // result, which is OK since multiplicative mutational effects are clipped at a minimum of 0.0. + slim_effect_t effect_size = traitInfoRec.effect_size_; + + if (effect_size == (slim_effect_t)0.0) + return (slim_effect_t)0.5; + if (effect_size <= -1.0) + return (slim_effect_t)1.0; + + // do the math in double-precision float to avoid numerical error + return (slim_effect_t)((std::sqrt(1.0 + (double)effect_size) - 1.0) / (double)effect_size); + } + } + + return traitInfoRec.dominance_coeff_UNSAFE_; +} + // This should be called whenever a mutation effect is changed; it handles the necessary recaching -void Mutation::SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect) +void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect) { slim_effect_t old_effect = traitInfoRec->effect_size_; - slim_effect_t dominance = traitInfoRec->dominance_coeff_; - slim_effect_t hemizygous_dominance = traitInfoRec->hemizygous_dominance_coeff_; + + if (old_effect == p_new_effect) + return; traitInfoRec->effect_size_ = p_new_effect; @@ -360,9 +498,11 @@ void Mutation::SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, s species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags } - // cache values used by the fitness calculation code for speed; see header + slim_effect_t realized_dominance = RealizedDominanceForTrait(p_trait); + slim_effect_t hemizygous_dominance = traitInfoRec->hemizygous_dominance_coeff_; - if (traitType == TraitType::kMultiplicative) + // cache values used by the fitness calculation code for speed; see header + if (p_trait->Type() == TraitType::kMultiplicative) { // For multiplicative traits, we clamp the lower end to 0.0; you can't be more lethal than lethal, and we // never want to go negative and then go positive again by multiplying in another negative effect. There @@ -370,66 +510,89 @@ void Mutation::SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, s // trait with no direct connection to fitness, then maybe clamping here would not make sense? But even // then, negative effects don't really seem to me to make sense there, so I think this is good. traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_effect); // 1 + s - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + dominance * p_new_effect); // 1 + hs - traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * p_new_effect); // 1 + hs (using hemizygous h) + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * p_new_effect); // 1 + hs + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * p_new_effect); // 1 + hs (using h_hemi) } - else // (traitType == TraitType::kAdditive) + else // (p_trait->Type() == TraitType::kAdditive) { // For additive traits, the baseline of the trait is arbitrary and there is no cutoff. traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * p_new_effect); // 2a - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * dominance * p_new_effect); // 2ha - traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * p_new_effect); // 2ha (using hemizygous h) + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * p_new_effect); // 2ha + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * p_new_effect); // 2ha (using h_hemi) } } - else // p_new_effect == 0.0 + else // p_new_effect == 0.0; therefore, old_effect != 0.0 { - if (old_effect != 0.0) + // Changing from non-neutral to neutral; determine whether the whole mutation is now neutral + // This is a bit complicated, but I don't expect this case to be hit very often + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + int trait_count = species.TraitCount(); + + is_neutral_ = true; + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - // Changing from non-neutral to neutral; various observers care about that - // Note that we cannot set is_neutral_ and other such flags to true here, because only this trait's - // effect has changed to neutral; other trait effects might be non-neutral, which we don't check - Species &species = mutation_type_ptr_->species_; - - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags - - // cache values used by the fitness calculation code for speed; see header - // for a neutral trait, we can set up this info very quickly - if (traitType == TraitType::kMultiplicative) + if ((mut_trait_info + trait_index)->effect_size_ != 0.0) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; - traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; - traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; - } - else // (traitType == TraitType::kAdditive) - { - traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; - traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; - traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + is_neutral_ = false; + break; } } + + // Note that we cannot set species.pure_neutral_ and mutation_type_ptr_->all_pure_neutral_DES_ to + // false here, because only this mutation has changed to neutral; other mutations might be non-neutral + + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags + + // cache values used by the fitness calculation code for speed; see header + // for a neutral trait, we can set up this info very quickly + if (p_trait->Type() == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)1.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)1.0; + } + else // (p_trait->Type() == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->heterozygous_effect_ = (slim_effect_t)0.0; + traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; + } } } // This should be called whenever a mutation dominance is changed; it handles the necessary recaching -void Mutation::SetDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) +void Mutation::SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) { - traitInfoRec->dominance_coeff_ = p_new_dominance; + traitInfoRec->dominance_coeff_UNSAFE_ = p_new_dominance; + + // set the is_independent_dominance_ flag according to p_new_dominance; if this produces an inconsistency + // with dominance values for other traits, it will be caught by the consistency check at the end + if (std::isnan(p_new_dominance)) + is_independent_dominance_ = true; + else + is_independent_dominance_ = false; - // We only need to recache the heterozygous_effect_ values, since only they are affected by the change in + // We only need to recache the heterozygous_effect_ value, since only it is affected by the change in // dominance coefficient. Changing dominance has no effect on is_neutral_ or any of the other is-neutral // flags. So this is very simple. - if (traitType == TraitType::kMultiplicative) + slim_effect_t effect_size = traitInfoRec->effect_size_; + slim_effect_t realized_dominance = RealizedDominanceForTrait(p_trait); + + if (p_trait->Type() == TraitType::kMultiplicative) { - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_dominance * traitInfoRec->effect_size_); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect_size); } - else // (traitType == TraitType::kAdditive) + else // (p_trait->Type() == TraitType::kAdditive) { - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); } } -void Mutation::SetHemizygousDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) +void Mutation::SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) { traitInfoRec->hemizygous_dominance_coeff_ = p_new_dominance; @@ -437,11 +600,11 @@ void Mutation::SetHemizygousDominance(TraitType traitType, MutationTraitInfo *tr // dominance coefficient. Changing dominance has no effect on is_neutral_ or any of the other is-neutral // flags. So this is very simple. - if (traitType == TraitType::kMultiplicative) + if (p_trait->Type() == TraitType::kMultiplicative) { traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_dominance * traitInfoRec->effect_size_); } - else // (traitType == TraitType::kAdditive) + else // (p_trait->Type() == TraitType::kAdditive) { traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); } @@ -508,6 +671,10 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(mutation_id_)); case gID_isFixed: // ACCELERATED return (((state_ == MutationState::kFixedAndSubstituted) || (state_ == MutationState::kRemovedWithSubstitution)) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + case gID_isIndependentDominance: // ACCELERATED + return (is_independent_dominance_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + case gID_isNeutral: // ACCELERATED + return (is_neutral_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_isSegregating: // ACCELERATED return ((state_ == MutationState::kInRegistry) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_mutationType: // ACCELERATED @@ -548,25 +715,30 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) { // This is not accelerated, because it's a bit tricky; each mutation could belong to a different species, // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + // Note that we use RealizedDominanceForTrait() here so that an independent dominance of NAN gets handled. Species &species = mutation_type_ptr_->species_; - MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); const std::vector &traits = species.Traits(); size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[0].dominance_coeff_)); + { + slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[0]); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + } else if (trait_count == 0) + { return gStaticEidosValue_Float_ZeroVec; + } else { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) { - slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_; + slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[trait_index]); - float_result->push_float_no_check(dominance); + float_result->push_float_no_check(realized_dominance); } return EidosValue_SP(float_result); @@ -651,8 +823,6 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = mutation_type_ptr_->species_; - MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) @@ -661,7 +831,12 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) Trait *trait = species.TraitFromName(trait_name); if (trait) + { + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].effect_size_)); + } } else if ((property_string.length() > 19) && Eidos_string_hasSuffix(property_string, "HemizygousDominance")) { @@ -669,7 +844,12 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) Trait *trait = species.TraitFromName(trait_name); if (trait) + { + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].hemizygous_dominance_coeff_)); + } } else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) { @@ -677,7 +857,12 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) Trait *trait = species.TraitFromName(trait_name); if (trait) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].dominance_coeff_)); + { + // Note that we use RealizedDominanceForTrait() here so that an independent dominance of NAN gets handled. + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + } } return super::GetProperty(p_property_id); @@ -714,6 +899,36 @@ EidosValue *Mutation::GetProperty_Accelerated_isFixed(EidosGlobalStringID p_prop return logical_result; } +EidosValue *Mutation::GetProperty_Accelerated_isIndependentDominance(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) +{ +#pragma unused (p_property_id) + EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + Mutation *value = (Mutation *)(p_values[value_index]); + + logical_result->set_logical_no_check(value->is_independent_dominance_, value_index); + } + + return logical_result; +} + +EidosValue *Mutation::GetProperty_Accelerated_isNeutral(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) +{ +#pragma unused (p_property_id) + EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + Mutation *value = (Mutation *)(p_values[value_index]); + + logical_result->set_logical_no_check(value->is_neutral_, value_index); + } + + return logical_result; +} + EidosValue *Mutation::GetProperty_Accelerated_isSegregating(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { #pragma unused (p_property_id) @@ -919,7 +1134,10 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); slim_effect_t new_effect = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); - SetEffect(trait->Type(), traitInfoRec, new_effect); + // FIXME MULTITRAIT: finite values only! + + SetEffect(trait, traitInfoRec, new_effect); + SelfConsistencyCheck(" after setting " + property_string); return; } } @@ -933,7 +1151,10 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); - SetHemizygousDominance(trait->Type(), traitInfoRec, new_dominance); + // FIXME MULTITRAIT: finite values only! + + SetHemizygousDominance(trait, traitInfoRec, new_dominance); + SelfConsistencyCheck(" after setting " + property_string); return; } } @@ -947,7 +1168,12 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); - SetDominance(trait->Type(), traitInfoRec, new_dominance); + // FIXME MULTITRAIT: NAN should be allowed, but only if (1) there is only one trait, + // or (2) the mutation is already set to independent dominance; can't change one + // dominance coefficient among many to be independent + + SetDominance(trait, traitInfoRec, new_dominance); + SelfConsistencyCheck(" after setting " + property_string); return; } } @@ -1055,19 +1281,18 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "dominanceForTrait"); // get the trait info for this mutation - MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); - if (trait_indices.size() == 1) { int64_t trait_index = trait_indices[0]; - slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_; + Trait *trait = traits[trait_index]; + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); } else { @@ -1075,9 +1300,10 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me for (int64_t trait_index : trait_indices) { - slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_; + Trait *trait = traits[trait_index]; + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - float_result->push_float_no_check(dominance); + float_result->push_float_no_check(realized_dominance); } return EidosValue_SP(float_result); @@ -1177,6 +1403,8 @@ const std::vector *Mutation_Class::Properties(void) properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosome, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isFixed, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isFixed)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isIndependentDominance, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isIndependentDominance)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isNeutral, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isNeutral)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isSegregating, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isSegregating)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_mutationType)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotide, false, kEidosValueMaskString | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_nucleotide)); @@ -1274,7 +1502,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t effect = (slim_effect_t)muttype->DrawEffectForTrait(trait_index); - mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + mut->SetEffect(traits[trait_index], traitInfoRec, effect); } } } @@ -1294,7 +1522,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + mut->SetEffect(traits[trait_index], traitInfoRec, effect); } } else @@ -1308,7 +1536,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + mut->SetEffect(traits[trait_index], traitInfoRec, effect); } } } @@ -1328,7 +1556,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + mut->SetEffect(traits[trait_index], traitInfoRec, effect); } } } @@ -1353,7 +1581,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t effect = static_cast(*(effects_int++)); - mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + mut->SetEffect(traits[trait_index], traitInfoRec, effect); } } else @@ -1368,7 +1596,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t effect = static_cast(*(effects_int++)); - mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + mut->SetEffect(traits[trait_index], traitInfoRec, effect); } } } @@ -1390,7 +1618,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t effect = static_cast(*(effects_float++)); - mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + mut->SetEffect(traits[trait_index], traitInfoRec, effect); } } else @@ -1405,7 +1633,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t effect = static_cast(*(effects_float++)); - mut->SetEffect(traits[trait_index]->Type(), traitInfoRec, effect); + mut->SetEffect(traits[trait_index], traitInfoRec, effect); } } } @@ -1414,6 +1642,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI else EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): setEffectForTrait() requires that effect be (a) NULL, requesting an effect value drawn from the mutation's mutation type for each trait, (b) singleton, providing one effect value for all traits, (c) equal in length to the number of traits in the species, providing one effect value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one effect value per trait per mutation." << EidosTerminate(); + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + mutations_buffer[mutation_index]->SelfConsistencyCheck(" after setEffectForTrait()"); + return gStaticEidosValueVOID; } @@ -1465,9 +1696,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri slim_effect_t dominance = ((p_method_id == gID_setDominanceForTrait) ? muttype->DefaultDominanceForTrait(trait_index) : muttype->DefaultHemizygousDominanceForTrait(trait_index)); if (p_method_id == gID_setDominanceForTrait) - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); else - mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); } } } @@ -1488,9 +1719,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; if (p_method_id == gID_setDominanceForTrait) - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); else - mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); } } else @@ -1505,9 +1736,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; if (p_method_id == gID_setDominanceForTrait) - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); else - mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); } } } @@ -1528,9 +1759,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; if (p_method_id == gID_setDominanceForTrait) - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); else - mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); } } } @@ -1556,9 +1787,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri slim_effect_t dominance = static_cast(*(dominances_int++)); if (p_method_id == gID_setDominanceForTrait) - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); else - mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); } } else @@ -1574,9 +1805,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri slim_effect_t dominance = static_cast(*(dominances_int++)); if (p_method_id == gID_setDominanceForTrait) - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); else - mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); } } } @@ -1599,9 +1830,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri slim_effect_t dominance = static_cast(*(dominances_float++)); if (p_method_id == gID_setDominanceForTrait) - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); else - mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); } } else @@ -1617,9 +1848,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri slim_effect_t dominance = static_cast(*(dominances_float++)); if (p_method_id == gID_setDominanceForTrait) - mut->SetDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); else - mut->SetHemizygousDominance(traits[trait_index]->Type(), traitInfoRec, dominance); + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); } } } @@ -1628,6 +1859,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri else EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_" << method_name << "): " << method_name << "() requires that dominance be (a) NULL, requesting the default" << ((p_method_id == gID_setDominanceForTrait) ? " " : " hemizygous ") << "dominance coefficient from the mutation's mutation type for each trait, (b) singleton, providing one dominance value for all traits, (c) equal in length to the number of traits in the species, providing one dominance value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one dominance value per trait per mutation." << EidosTerminate(); + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + mutations_buffer[mutation_index]->SelfConsistencyCheck(std::string(" after ") + method_name); + return gStaticEidosValueVOID; } diff --git a/core/mutation.h b/core/mutation.h index 26ef2c2c..d22a4a04 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -34,6 +34,7 @@ #include "eidos_value.h" class MutationType; +class Trait; extern EidosClass *gSLiM_Mutation_Class; @@ -57,10 +58,12 @@ typedef int32_t MutationIndex; // by each mutation -- is also determined at runtime. We don't want to make a separate malloced block for each mutation; // that would be far too expensive. Instead, MutationBlock keeps a block of MutationTraitInfo records for the species, // with a number of records per mutation that is determined when it is constructed. +// BCH 12/27/2025: Note that dominance_coeff_UNSAFE_ is marked "UNSAFE" because it can be NAN, representing independent +// dominance. For this reason, it should not be used directly; instead, use RealizedDominanceForTrait(). typedef struct _MutationTraitInfo { slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) - slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t dominance_coeff_UNSAFE_; // dominance coefficient (h), inherited from MutationType by default; CAN BE NAN slim_effect_t hemizygous_dominance_coeff_; // hemizygous dominance coefficient (h_hemi), inherited from MutationType by default // We cache values used in the fitness calculation code, for speed. These are the final fitness effects of this mutation @@ -96,12 +99,21 @@ class Mutation : public EidosDictionaryRetained slim_chromosome_index_t chromosome_index_; // the (uint8_t) index of this mutation's chromosome int state_ : 4; // see MutationState above; 4 bits so we can represent -1 - // is_neutral_ is true if all mutation effects are 0.0 (note this might be overridden by a callback); - // the 0 state is sticky, so if the mutation is ever marked non-neutral then it stays marked non-neutral, - // just because re-evaluating that requires scanning across the effects for all traits -- not worth it - // this is used to make constructing non-neutral caches for fitness evaluation fast with multiple traits + // is_neutral_ is true if all mutation effects are 0.0 (note this might be overridden by a callback). + // The state of is_neutral_ is updated to reflect the current state of the mutation whenever it changes. + // This is used to make constructing non-neutral caches for trait evaluation fast with multiple traits. unsigned int is_neutral_ : 1; + // is_independent_dominance_ is true if the mutation has been configured to exhibit "independent dominance", + // meaning that two heterozygous effects equal one homozygous effect, allowing the effects from haplosomes + // to be calculated separately with no regard for zygosity; this is configured by using NAN as the default + // dominance coefficient for MutationType. It is updated if the state of the mutation's dominance changes, + // but only based upon the special NAN dominance value in setDominanceForTrait(); setting dominance values + // that happen to produce independent dominance does not cause this flag to be set, only the special NAN + // value. This is used to construct independent-dominance caches for fast trait evaluation. Note that this + // flag can be true when is_neutral_ is also true, recording that independent dominance was configured. + unsigned int is_independent_dominance_ : 1; + int8_t nucleotide_; // the nucleotide being kept: A=0, C=1, G=2, T=3. -1 is used to indicate non-nucleotide-based. int8_t scratch_; // temporary scratch space for use by algorithms; regard as volatile outside your own code block const slim_mutationid_t mutation_id_; // a unique id for each mutation, used to track mutations @@ -131,11 +143,6 @@ class Mutation : public EidosDictionaryRetained // FIXME MULTITRAIT: needs to take a whole vector of each, per trait! Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); - // These should be called whenever a mutation effect/dominance is changed; they handle the necessary recaching - void SetEffect(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect); - void SetDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); - void SetHemizygousDominance(TraitType traitType, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); - // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline #if DEBUG_MUTATIONS inline virtual ~Mutation(void) override @@ -148,6 +155,17 @@ class Mutation : public EidosDictionaryRetained virtual void SelfDelete(void) override; + // Check that our internal state all makes sense + void SelfConsistencyCheck(const std::string &p_message_end); + + // This handles the possibility that a dominance coefficient is NAN, representing independent dominance, and returns the correct value + slim_effect_t RealizedDominanceForTrait(Trait *p_trait); + + // These should be called whenever a mutation effect/dominance is changed; they handle the necessary recaching + void SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect); + void SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); + void SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); + // // Eidos support // @@ -165,6 +183,8 @@ class Mutation : public EidosDictionaryRetained // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_isFixed(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_isIndependentDominance(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_isNeutral(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_isSegregating(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_nucleotideValue(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 1ace2cb2..622c1d65 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -90,7 +90,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr // set up DE entries for all traits; every trait is initialized identically, from the parameters given EffectDistributionInfo DES_info; - DES_info.default_dominance_coeff_ = static_cast(p_dominance_coeff); + DES_info.default_dominance_coeff_ = static_cast(p_dominance_coeff); // note this can be NAN now, representing independent dominance DES_info.default_hemizygous_dominance_coeff_ = 1.0; DES_info.DES_type_ = p_DES_type; DES_info.DES_parameters_ = p_DES_parameters; @@ -234,6 +234,33 @@ void MutationType::ParseDESParameters(std::string &p_DES_type_string, const Eido } } +void MutationType::SelfConsistencyCheck(const std::string &p_message_end) +{ + // note that we don't check for mutation_block_ being nullptr here because we get called before that happens, + // unlike SelfConsistencyCheck() for Mutation and Substitution, where the mutation block necessarily exists + const std::vector &traits = species_.Traits(); + + if (effect_distributions_.size() != traits.size()) + EIDOS_TERMINATION << "ERROR (MutationType::SelfConsistencyCheck): (internal error) effect_distributions_ size does not match traits.size()" << p_message_end << "." << EidosTerminate(); + + if (effect_distributions_.size() > 0) + { + bool is_independent_dominance = std::isnan(effect_distributions_[0].default_dominance_coeff_); + + for (EffectDistributionInfo &des_info : effect_distributions_) + { + if (std::isnan(des_info.default_dominance_coeff_) != is_independent_dominance) + EIDOS_TERMINATION << "ERROR (MutationType::SelfConsistencyCheck): mutation type independent dominance state is inconsistent" << p_message_end << "." << EidosTerminate(); + + if (std::isinf(des_info.default_dominance_coeff_)) // NAN allowed + EIDOS_TERMINATION << "ERROR (MutationType::SelfConsistencyCheck): mutation type default dominance is infinite" << p_message_end << "." << EidosTerminate(); + + if (!std::isfinite(des_info.default_hemizygous_dominance_coeff_)) + EIDOS_TERMINATION << "ERROR (MutationType::SelfConsistencyCheck): mutation type default hemizygous dominance is non-finite" << p_message_end << "." << EidosTerminate(); + } + } +} + slim_effect_t MutationType::DrawEffectForTrait(int64_t p_trait_index) const { const EffectDistributionInfo &DES_info = effect_distributions_[p_trait_index]; @@ -910,6 +937,8 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba // still want to let the community know that a mutation type has changed, though. species_.community_.mutation_types_changed_ = true; + SelfConsistencyCheck(" in setDefaultDominanceForTrait()"); + return gStaticEidosValueVOID; } @@ -958,6 +987,8 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( // still want to let the community know that a mutation type has changed, though. species_.community_.mutation_types_changed_ = true; + SelfConsistencyCheck(" in setDefaultDominanceForTrait()"); + return gStaticEidosValueVOID; } diff --git a/core/mutation_type.h b/core/mutation_type.h index 20ea758d..f58afacb 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -181,6 +181,9 @@ class MutationType : public EidosDictionaryUnretained static void ParseDESParameters(std::string &p_DES_type_string, const EidosValue_SP *const p_arguments, int p_argument_count, DESType *p_DES_type, std::vector *p_DES_parameters, std::vector *p_DES_strings); + // Check that our internal state all makes sense + void SelfConsistencyCheck(const std::string &p_message_end); + slim_effect_t DefaultDominanceForTrait(int64_t p_trait_index) const { const EffectDistributionInfo &DES_info = effect_distributions_[p_trait_index]; diff --git a/core/polymorphism.cpp b/core/polymorphism.cpp index 90cc8703..bc618ef6 100644 --- a/core/polymorphism.cpp +++ b/core/polymorphism.cpp @@ -69,8 +69,15 @@ void Polymorphism::Print_ID_Tag(std::ostream &p_out) const if (trait_index > 0) p_out << ","; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].dominance_coeff_); // necessary precision for non-lossiness - p_out << double_buf; + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_UNSAFE_; // can be NAN + + if (std::isnan(dominance)) + p_out << "NAN"; + else + { + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, dominance); // necessary precision for non-lossiness + p_out << double_buf; + } } p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -123,8 +130,15 @@ void Polymorphism::Print_ID(std::ostream &p_out) const if (trait_index > 0) p_out << ","; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].dominance_coeff_); // necessary precision for non-lossiness - p_out << double_buf; + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_UNSAFE_; // can be NAN + + if (std::isnan(dominance)) + p_out << "NAN"; + else + { + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, dominance); // necessary precision for non-lossiness + p_out << double_buf; + } } p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -177,7 +191,12 @@ void Polymorphism::Print_NoID_Tag(std::ostream &p_out) const if (trait_index > 0) p_out << ","; - p_out << mut_trait_info[trait_index].dominance_coeff_; + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_UNSAFE_; // can be NAN + + if (std::isnan(dominance)) + p_out << "NAN"; + else + p_out << dominance; } // and then the remainder of the output line @@ -239,7 +258,12 @@ void Polymorphism::Print_NoID(std::ostream &p_out) const if (trait_index > 0) p_out << ","; - p_out << mut_trait_info[trait_index].dominance_coeff_; + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_UNSAFE_; // can be NAN + + if (std::isnan(dominance)) + p_out << "NAN"; + else + p_out << dominance; } // and then the remainder of the output line diff --git a/core/population.cpp b/core/population.cpp index ddd1a823..2b752525 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -8174,7 +8174,7 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit // FIXME MULTITRAIT: for now we just write out trait 0, need to write out all of them with a count... slim_effect_t selection_coeff = mut_trait_info->effect_size_; - slim_effect_t dominance_coeff = mut_trait_info->dominance_coeff_; + slim_effect_t dominance_coeff = mut_trait_info->dominance_coeff_UNSAFE_; // can be NAN // BCH 9/22/2021: Note that mutation_type_ptr->hemizygous_dominance_coeff_ is not saved; too edge to be bothered... // FIXME MULTITRAIT: This will now change, since the hemizygous dominance coefficient is becoming a first-class citizen @@ -8335,7 +8335,7 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit // FIXME MULTITRAIT: for now we just write out trait 0, need to write out all of them with a count... slim_effect_t selection_coeff = substitution_ptr->trait_info_[0].effect_size_; - slim_effect_t dominance_coeff = substitution_ptr->trait_info_[0].dominance_coeff_; + slim_effect_t dominance_coeff = substitution_ptr->trait_info_[0].dominance_coeff_UNSAFE_; // can be NAN slim_objectid_t subpop_index = substitution_ptr->subpop_index_; slim_tick_t origin_tick = substitution_ptr->origin_tick_; diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 44ba2c8a..19f1e5e4 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1245,6 +1245,8 @@ const std::string &gStr_mutationTypes = EidosRegisteredString("mutationTypes", g const std::string &gStr_mutationFractions = EidosRegisteredString("mutationFractions", gID_mutationFractions); const std::string &gStr_mutationMatrix = EidosRegisteredString("mutationMatrix", gID_mutationMatrix); const std::string &gStr_isFixed = EidosRegisteredString("isFixed", gID_isFixed); +const std::string &gStr_isIndependentDominance = EidosRegisteredString("isIndependentDominance", gID_isIndependentDominance); +const std::string &gStr_isNeutral = EidosRegisteredString("isNeutral", gID_isNeutral); const std::string &gStr_isSegregating = EidosRegisteredString("isSegregating", gID_isSegregating); const std::string &gStr_mutationType = EidosRegisteredString("mutationType", gID_mutationType); const std::string &gStr_nucleotide = EidosRegisteredString("nucleotide", gID_nucleotide); diff --git a/core/slim_globals.h b/core/slim_globals.h index daacd70d..b6ca41ed 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -834,6 +834,8 @@ extern const std::string &gStr_mutationTypes; extern const std::string &gStr_mutationFractions; extern const std::string &gStr_mutationMatrix; extern const std::string &gStr_isFixed; +extern const std::string &gStr_isIndependentDominance; +extern const std::string &gStr_isNeutral; extern const std::string &gStr_isSegregating; extern const std::string &gStr_mutationType; extern const std::string &gStr_nucleotide; @@ -1319,6 +1321,8 @@ enum _SLiMGlobalStringID : int { gID_mutationFractions, gID_mutationMatrix, gID_isFixed, + gID_isIndependentDominance, + gID_isNeutral, gID_isSegregating, gID_mutationType, gID_nucleotide, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 11cfe542..2c102c6e 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1213,6 +1213,54 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.weightHemizygousDominance, 1.0)) stop(); }"); } + // Test independent dominance and new Mutation and MutationType APIs + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5); if (m1.defaultDominanceForTrait(0) == 0.5) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN); if (isNAN(m1.defaultDominanceForTrait(0))) stop(); }"); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', INF); }", "requires dominanceCoeff to be finite", __LINE__); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(0, NAN); if (isNAN(m1.defaultDominanceForTrait(0))) stop(); }"); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(0, INF); }", "default dominance is infinite", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultHemizygousDominanceForTrait(0, NAN); }", "hemizygous dominance is non-finite", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultHemizygousDominanceForTrait(0, INF); }", "hemizygous dominance is non-finite", __LINE__); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); if (m1.defaultDominanceForTrait('A') == 0.5) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); if (isNAN(m1.defaultDominanceForTrait('A'))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); if (identical(m1.defaultDominanceForTrait(), c(0.5,0.5))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); if (identical(m1.defaultDominanceForTrait(), c(NAN,NAN))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(), c(0.25, 0.75))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('B','A'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(), c(0.75, 0.25))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('B','A'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(c('B','A')), c(0.25, 0.75))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(NAN, NAN)); if (identical(m1.defaultDominanceForTrait(), c(NAN, NAN))) stop(); }"); + SLiMAssertScriptRaise("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A'), NAN); }", "independent dominance state is inconsistent", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); }", "independent dominance state is inconsistent", __LINE__); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, 0.5)); if (identical(m1.defaultDominanceForTrait(), c(0.5, 0.5))) stop(); }"); + SLiMAssertScriptRaise("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A'), 0.5); }", "independent dominance state is inconsistent", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); }", "independent dominance state is inconsistent", __LINE__); + + std::string middle = " initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeMutationRate(1e-4); } 1 late() { sim.addSubpop('p1', 10); } 2 late() { muts = sim.mutations; "; + + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.isNeutral == T)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.isIndependentDominance == F)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.dominance == 0.5)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.effect == 0.0)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.isNeutral == F)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.isIndependentDominance == F)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.dominance == 0.5)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (allClose(muts.effect, 0.0001)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.isNeutral == T)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.isIndependentDominance == T)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.dominance == 0.5)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.effect == 0.0)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (all(muts.isNeutral == F)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (all(muts.isIndependentDominance == T)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (allClose(muts.dominance, 0.4999875)) stop(); }"); // h = (sqrt(1+s)-1)/s + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (allClose(muts.effect, 0.0001)) stop(); }"); + + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.setDominanceForTrait(0, 0.5); if (all(muts.dominance == 0.5)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.heightDominance = 0.5; if (all(muts.dominance == 0.5)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.heightDominance = 0.5; if (all(muts.heightDominance == 0.5)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.setDominanceForTrait(0, NAN); if (allClose(muts.dominance, 0.4999875)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.heightDominance = NAN; if (allClose(muts.dominance, 0.4999875)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.heightDominance = NAN; if (allClose(muts.heightDominance, 0.4999875)) stop(); }"); + std::cout << "_RunMultitraitTests() done" << std::endl; } diff --git a/core/species.cpp b/core/species.cpp index 720cab00..3b15a817 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -192,7 +192,7 @@ Species::~Species(void) delete trait; traits_.clear(); - // Free our MutationBlock, and make those with copies of it forget it; see CreateAndPromulgateMutationBlock + // Free our MutationBlock, and make those with pointers to it forget; see CreateAndPromulgateMutationBlock() { delete mutation_block_; mutation_block_ = nullptr; @@ -2840,6 +2840,9 @@ void Species::RunInitializeCallbacks(void) void Species::CreateAndPromulgateMutationBlock(void) { + // This creates a new MutationBlock and gives pointers to it to various sub-components of the species. This + // is called toward the end of initialize() callbacks; note that pointers will be nullptr until then. That + // is because we can't allocate the MutationBlock until we know how many traits there are. if (mutation_block_) EIDOS_TERMINATION << "ERROR (Species::CreateAndPromulgateMutationBlock): (internal error) a mutation block has already been allocated." << EidosTerminate(); @@ -9915,6 +9918,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapDefaultDominanceForTrait(0) /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); // FIXME MULTITRAIT population_.treeseq_substitutions_map_.emplace(position, sub); @@ -9929,6 +9933,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapNewMutationFromBlock(); // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec; likewise hemizygous dominance + // FIXME MULTITRAIT this code will also now need to handle the independent dominance case, for which NaN should be in the metadata Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->DefaultDominanceForTrait(0) /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); // FIXME MULTITRAIT // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry diff --git a/core/species.h b/core/species.h index d7bbbb4a..397de0df 100644 --- a/core/species.h +++ b/core/species.h @@ -528,7 +528,7 @@ class Species : public EidosDictionaryUnretained inline __attribute__((always_inline)) slim_tick_t TickPhase(void) { return tick_phase_; } inline __attribute__((always_inline)) bool HasGenetics(void) { return has_genetics_; } - inline __attribute__((always_inline)) MutationBlock *SpeciesMutationBlock(void) { return mutation_block_; } // FIXME MULTICHROM: We could cache a ref to this in some key spots like Chromosome, MutationType, and Subpopulation + inline __attribute__((always_inline)) MutationBlock *SpeciesMutationBlock(void) { return mutation_block_; } inline __attribute__((always_inline)) const std::map &MutationTypes(void) const { return mutation_types_; } inline __attribute__((always_inline)) const std::map &GenomicElementTypes(void) { return genomic_element_types_; } inline __attribute__((always_inline)) size_t GraveyardSize(void) const { return graveyard_.size(); } diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 6756dfae..cdcf52e1 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -614,6 +614,10 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: slim_objectid_t map_identifier = SLiM_ExtractObjectIDFromEidosValue_is(id_value, 0, 'm'); double dominance_coeff = dominanceCoeff_value->NumericAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(dominance_coeff) && !std::isnan(dominance_coeff)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeMutationType): " << p_function_name << "() requires dominanceCoeff to be finite, or NAN to represent independent dominance." << EidosTerminate(); + std::string DES_type_string = (defaultDistribution ? "f" : distributionType_value->StringAtIndex_NOCAST(0, nullptr)); if (community_.MutationTypeWithID(map_identifier)) diff --git a/core/substitution.cpp b/core/substitution.cpp index 687b2781..19ef842f 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -24,6 +24,7 @@ #include "eidos_property_signature.h" #include "species.h" #include "mutation_block.h" +#include "trait.h" #include #include @@ -36,7 +37,7 @@ #pragma mark - Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : - EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) +EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), is_neutral_(p_mutation.is_neutral_), is_independent_dominance_(p_mutation.is_independent_dominance_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) { AddKeysAndValuesFrom(&p_mutation); @@ -53,9 +54,13 @@ Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : for (int trait_index = 0; trait_index < trait_count; trait_index++) { trait_info_[trait_index].effect_size_ = mut_trait_info[trait_index].effect_size_; - trait_info_[trait_index].dominance_coeff_ = mut_trait_info[trait_index].dominance_coeff_; + trait_info_[trait_index].dominance_coeff_UNSAFE_ = mut_trait_info[trait_index].dominance_coeff_UNSAFE_; // can be NAN trait_info_[trait_index].hemizygous_dominance_coeff_ = mut_trait_info[trait_index].hemizygous_dominance_coeff_; } + +#if DEBUG + SelfConsistencyCheck(" in Substitution::Substitution()"); +#endif } Substitution::Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide) : @@ -68,16 +73,95 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ trait_info_ = (SubstitutionTraitInfo *)malloc(trait_count * sizeof(SubstitutionTraitInfo)); + // We need to infer the values of the is_neutral_ and is_independent_dominance_ flags + // FIXME MULTITRAIT: needs to be fixed when the below issues are fixed + is_neutral_ = (p_selection_coeff == 0.0); + is_independent_dominance_ = std::isnan(p_dominance_coeff); + trait_info_[0].effect_size_ = p_selection_coeff; - trait_info_[0].dominance_coeff_ = p_dominance_coeff; + trait_info_[0].dominance_coeff_UNSAFE_ = p_dominance_coeff; // can be NAN trait_info_[0].hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(0); // FIXME MULTITRAIT: needs to be passed in for (int trait_index = 1; trait_index < trait_count; trait_index++) { - trait_info_[trait_index].effect_size_ = 0.0; - trait_info_[trait_index].dominance_coeff_ = 0.0; + trait_info_[trait_index].effect_size_ = 0.0; // FIXME MULTITRAIT: needs to be passed in + trait_info_[trait_index].dominance_coeff_UNSAFE_ = 0.0; // FIXME MULTITRAIT: needs to be passed in trait_info_[trait_index].hemizygous_dominance_coeff_ = 1.0; // FIXME MULTITRAIT: needs to be passed in } + +#if DEBUG + SelfConsistencyCheck(" in Substitution::Substitution()"); +#endif +} + +void Substitution::SelfConsistencyCheck(const std::string &p_message_end) +{ + if (!mutation_type_ptr_) + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): (internal error) mutation_type_ptr_ is nullptr" << p_message_end << "." << EidosTerminate(); + if (!trait_info_) + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): (internal error) trait_info_ is nullptr" << p_message_end << "." << EidosTerminate(); + + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + int trait_count = (int)traits.size(); + bool all_neutral_effects = true; + + for (int trait_index = 0; trait_index < trait_count; ++trait_index) + { + SubstitutionTraitInfo &traitInfoRec = trait_info_[trait_index]; + + if (!std::isfinite(traitInfoRec.effect_size_)) + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution effect size is non-finite" << p_message_end << "." << EidosTerminate(); + if (std::isinf(traitInfoRec.dominance_coeff_UNSAFE_)) // NAN is legal sometimes, checked below + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution dominance is infinite" << p_message_end << "." << EidosTerminate(); + if (!std::isfinite(traitInfoRec.hemizygous_dominance_coeff_)) + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution hemizygous dominance is non-finite" << p_message_end << "." << EidosTerminate(); + + if ((is_independent_dominance_ && !std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) || + (!is_independent_dominance_ && std::isnan(traitInfoRec.dominance_coeff_UNSAFE_))) + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution independent dominance state is inconsistent" << p_message_end << "." << EidosTerminate(); + + if (traitInfoRec.effect_size_ != 0.0) + all_neutral_effects = false; + } + + if ((is_neutral_ && !all_neutral_effects) || (!is_neutral_ && all_neutral_effects)) + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution neutrality state is inconsistent" << p_message_end << "." << EidosTerminate(); +} + +slim_effect_t Substitution::RealizedDominanceForTrait(Trait *p_trait) +{ + int64_t trait_index = p_trait->Index(); + SubstitutionTraitInfo &traitInfoRec = trait_info_[trait_index]; + + if (std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) + { + // NAN indicates independent dominance and needs to be handled specially here + if (p_trait->Type() == TraitType::kAdditive) + { + // for additive traits independent dominance is always 0.5 + return 0.5; + } + else + { + // for multiplicative traits the dominance is calculated as (sqrt(1+s)-1)/s, except that the effect + // is clamped to a minimum of -1.0 to avoid a negative square root; this is correct, since it means + // that 1+s (1 + -1 == 0) equals 2(1+hs): (2 x (1 + 1 x -1)) == (2 x 0) == 0. If the resulting + // dominance of 1.0 is used in 1+hs with the unclipped effect size, a negative mutational effect will + // result, which is OK since multiplicative mutational effects are clipped at a minimum of 0.0. + slim_effect_t effect_size = traitInfoRec.effect_size_; + + if (effect_size == (slim_effect_t)0.0) + return (slim_effect_t)0.5; + if (effect_size <= -1.0) + return (slim_effect_t)1.0; + + // do the math in double-precision float to avoid numerical error + return (slim_effect_t)((std::sqrt(1.0 + (double)effect_size) - 1.0) / (double)effect_size); + } + } + + return traitInfoRec.dominance_coeff_UNSAFE_; } void Substitution::PrintForSLiMOutput(std::ostream &p_out) const @@ -101,7 +185,19 @@ void Substitution::PrintForSLiMOutput(std::ostream &p_out) const int trait_count = species.TraitCount(); for (int trait_index = 0; trait_index < trait_count; ++trait_index) - p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; // FIXME MULTITRAIT: hemizygous dominance coeff? + { + p_out << " " << trait_info_[trait_index].effect_size_ << " "; + + // output the dominance; if it is NAN for independent dominance we output NAN to preserve a record of that fact + slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_UNSAFE_; + + if (std::isnan(dominance)) + p_out << "NAN"; + else + p_out << dominance; + + // FIXME MULTITRAIT: hemizygous dominance coeff? + } // and then the remainder of the output line p_out << " p" << subpop_index_ << " " << origin_tick_ << " " << fixation_tick_; @@ -136,7 +232,19 @@ void Substitution::PrintForSLiMOutput_Tag(std::ostream &p_out) const int trait_count = species.TraitCount(); for (int trait_index = 0; trait_index < trait_count; ++trait_index) - p_out << " " << trait_info_[trait_index].effect_size_ << " " << trait_info_[trait_index].dominance_coeff_; // FIXME MULTITRAIT: hemizygous dominance coeff? + { + p_out << " " << trait_info_[trait_index].effect_size_ << " "; + + // output the dominance; if it is NAN for independent dominance we output NAN to preserve a record of that fact + slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_UNSAFE_; + + if (std::isnan(dominance)) + p_out << "NAN"; + else + p_out << dominance; + + // FIXME MULTITRAIT: hemizygous dominance coeff? + } // and then the remainder of the output line p_out << " p" << subpop_index_ << " " << origin_tick_ << " " << fixation_tick_; @@ -189,6 +297,10 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) } case gID_id: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(mutation_id_)); + case gID_isIndependentDominance: // ACCELERATED + return (is_independent_dominance_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + case gID_isNeutral: // ACCELERATED + return (is_neutral_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_mutationType: // ACCELERATED return mutation_type_ptr_->SymbolTableEntry().second; case gID_position: // ACCELERATED @@ -223,23 +335,30 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) { // This is not accelerated, because it's a bit tricky; each substitution could belong to a different species, // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. + // Note that we use RealizedDominanceForTrait() here so that an independent dominance of NAN gets handled. Species &species = mutation_type_ptr_->species_; const std::vector &traits = species.Traits(); size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[0].dominance_coeff_)); + { + slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[0]); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + } else if (trait_count == 0) + { return gStaticEidosValue_Float_ZeroVec; + } else { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) { - slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_; + slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[trait_index]); - float_result->push_float_no_check(dominance); + float_result->push_float_no_check(realized_dominance); } return EidosValue_SP(float_result); @@ -350,7 +469,12 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) Trait *trait = species.TraitFromName(trait_name); if (trait) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].dominance_coeff_)); + { + // Note that we use RealizedDominanceForTrait() here so that an independent dominance of NAN gets handled. + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + } } return super::GetProperty(p_property_id); @@ -372,6 +496,36 @@ EidosValue *Substitution::GetProperty_Accelerated_id(EidosGlobalStringID p_prope return int_result; } +EidosValue *Substitution::GetProperty_Accelerated_isIndependentDominance(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) +{ +#pragma unused (p_property_id) + EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + Substitution *value = (Substitution *)(p_values[value_index]); + + logical_result->set_logical_no_check(value->is_independent_dominance_, value_index); + } + + return logical_result; +} + +EidosValue *Substitution::GetProperty_Accelerated_isNeutral(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) +{ +#pragma unused (p_property_id) + EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + Substitution *value = (Substitution *)(p_values[value_index]); + + logical_result->set_logical_no_check(value->is_neutral_, value_index); + } + + return logical_result; +} + EidosValue *Substitution::GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { #pragma unused (p_property_id) @@ -592,15 +746,17 @@ EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "dominanceForTrait"); if (trait_indices.size() == 1) { int64_t trait_index = trait_indices[0]; - slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_; + Trait *trait = traits[trait_index]; + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); } else { @@ -608,9 +764,10 @@ EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID for (int64_t trait_index : trait_indices) { - slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_; + Trait *trait = traits[trait_index]; + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - float_result->push_float_no_check(dominance); + float_result->push_float_no_check(realized_dominance); } return EidosValue_SP(float_result); @@ -672,19 +829,21 @@ const std::vector *Substitution_Class::Properties(vo properties = new std::vector(*super::Properties()); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosome, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class))); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_id)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_mutationType)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_position)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effect, true, kEidosValueMaskFloat))); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominance,true, kEidosValueMaskFloat))); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_subpopID)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotide, true, kEidosValueMaskString | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotide)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotideValue)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_originTick)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_fixationTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_fixationTick)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_tag)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosome, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_id)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isIndependentDominance, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isIndependentDominance)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isNeutral, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isNeutral)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_mutationType)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_position)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effect, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominance, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_subpopID)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotide, true, kEidosValueMaskString | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotide)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_nucleotideValue)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_originTick)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_fixationTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_fixationTick)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_tag)); std::sort(properties->begin(), properties->end(), CompareEidosPropertySignatures); } diff --git a/core/substitution.h b/core/substitution.h index 8c57fbed..baa74842 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -34,6 +34,8 @@ #include "chromosome.h" #include "eidos_value.h" +class Trait; + extern EidosClass *gSLiM_Substitution_Class; @@ -44,10 +46,12 @@ extern EidosClass *gSLiM_Substitution_Class; // but keeps less information since it is not used during fitness evaluation. Also unlike Mutation, which keeps all this // in a block maintained by MutationBlock, we simply make a malloced block for each substitution; substitution is relatively // rare and substitutions don't go away once created, so there is no need to overcomplicate this design. +// BCH 12/27/2025: Note that dominance_coeff_UNSAFE_ is marked "UNSAFE" because it can be NAN, representing independent +// dominance. For this reason, it should not be used directly; instead, use RealizedDominanceForTrait(). typedef struct _SubstitutionTraitInfo { slim_effect_t effect_size_; // selection coefficient (s) or additive effect (a) - slim_effect_t dominance_coeff_; // dominance coefficient (h), inherited from MutationType by default + slim_effect_t dominance_coeff_UNSAFE_; // dominance coefficient (h), inherited from MutationType by default; CAN BE NAN slim_effect_t hemizygous_dominance_coeff_; // hemizygous dominance coefficient (h_hemi), inherited from MutationType by default } SubstitutionTraitInfo; @@ -66,6 +70,10 @@ class Substitution : public EidosDictionaryRetained slim_tick_t origin_tick_; // tick in which mutation arose slim_tick_t fixation_tick_; // tick in which mutation fixed slim_chromosome_index_t chromosome_index_; // the (uint8_t) index of this mutation's chromosome + + unsigned int is_neutral_ : 1; // all effects are 0.0; see mutation.h + unsigned int is_independent_dominance_ : 1; // configured for "independent dominance"; see mutation.h + int8_t nucleotide_; // the nucleotide being kept: A=0, C=1, G=2, T=3. -1 is used to indicate non-nucleotide-based. const slim_mutationid_t mutation_id_; // a unique id for each mutation, used to track mutations slim_usertag_t tag_value_; // a user-defined tag value @@ -79,9 +87,14 @@ class Substitution : public EidosDictionaryRetained Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick); // construct from the mutation that has fixed, and the tick in which it fixed Substitution(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, slim_tick_t p_fixation_tick, int8_t p_nucleotide); - // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though inline virtual ~Substitution(void) override { free(trait_info_); trait_info_ = nullptr; } + // Check that our internal state all makes sense + void SelfConsistencyCheck(const std::string &p_message_end); + + // This handles the possibility that a dominance coefficient is NAN, representing independent dominance, and returns the correct value + slim_effect_t RealizedDominanceForTrait(Trait *p_trait); + void PrintForSLiMOutput(std::ostream &p_out) const; void PrintForSLiMOutput_Tag(std::ostream &p_out) const; @@ -100,6 +113,8 @@ class Substitution : public EidosDictionaryRetained // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_isIndependentDominance(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_isNeutral(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_nucleotideValue(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_originTick(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); From 700b2dd49e949c9fcbf9f374979f281edac7c205 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 28 Dec 2025 18:05:17 -0600 Subject: [PATCH 046/107] split all_pure_neutral_DES_ into two flags --- QtSLiM/QtSLiMChromosomeWidget.cpp | 2 +- SLiMgui/ChromosomeView.mm | 2 +- VERSIONS | 2 +- core/genomic_element_type.cpp | 3 +-- core/haplosome.cpp | 16 ++++++++-------- core/individual.cpp | 2 +- core/mutation.cpp | 17 ++++++----------- core/mutation_type.cpp | 29 ++++++++++++++++++++++------- core/mutation_type.h | 27 +++++++++++++++++---------- core/population.cpp | 6 +++--- core/species.cpp | 14 +++++++------- core/species_eidos.cpp | 2 +- core/subpopulation.cpp | 7 +++++-- 13 files changed, 74 insertions(+), 55 deletions(-) diff --git a/QtSLiM/QtSLiMChromosomeWidget.cpp b/QtSLiM/QtSLiMChromosomeWidget.cpp index 3eb3cecb..6223d938 100644 --- a/QtSLiM/QtSLiMChromosomeWidget.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget.cpp @@ -480,7 +480,7 @@ void QtSLiMChromosomeWidgetController::runChromosomeContextMenuAtPoint(QPoint p_ MutationType *muttype = muttype_iter.second; slim_objectid_t muttype_id = muttype->mutation_type_id_; - if (muttype->IsPureNeutralDES()) + if (muttype->all_neutral_DES_) // judges based on DES, not based on the actual neutrality of the mutations of this type! displayMuttypes_.emplace_back(muttype_id); } } diff --git a/SLiMgui/ChromosomeView.mm b/SLiMgui/ChromosomeView.mm index d2817da4..ba3e8d46 100644 --- a/SLiMgui/ChromosomeView.mm +++ b/SLiMgui/ChromosomeView.mm @@ -1028,7 +1028,7 @@ - (IBAction)filterNonNeutral:(id)sender MutationType *muttype = muttype_iter.second; slim_objectid_t muttype_id = muttype->mutation_type_id_; - if (!muttype->IsPureNeutralDES()) + if (!muttype->all_neutral_DES_) // judges based on DES, not based on the actual neutrality of the mutations of this type! display_muttypes_.emplace_back(muttype_id); } diff --git a/VERSIONS b/VERSIONS index 4758b666..fe5653ec 100644 --- a/VERSIONS +++ b/VERSIONS @@ -70,7 +70,7 @@ multitrait branch: add SLiMgui autofixing for all of the above changes in QtSLiMWindow::checkTerminationForAutofix() add MutationType method setDefaultDominanceForTrait(Nio trait, float dominance) (approximately replacing writing into the dominanceCoeff property, but this should not autofix) transition MutationType's internals to keep a separate DE for each trait using a new EffectDistributionInfo struct - added a C++ IsPureNeutralDES() method to represent whether all of the effects of a given mutation type are all neutral + added C++ all_neutral_DES_ and all_neutral_mutations_ flags to represent whether (a) all of the effects of a given mutation type are neutral, and (b) all mutations of that type are actually neutral make initializeMutationType()'s DES set up the DES for all traits (then separately configurable with setEffectDistributionForTrait()) add support in Individual for the individual's offset for each trait add -(float)offsetForTrait([Nio trait = NULL]) diff --git a/core/genomic_element_type.cpp b/core/genomic_element_type.cpp index cffe51fc..2615533f 100644 --- a/core/genomic_element_type.cpp +++ b/core/genomic_element_type.cpp @@ -407,8 +407,7 @@ EidosValue_SP GenomicElementType::ExecuteMethod_setMutationFractions(EidosGlobal mutation_fractions.emplace_back(proportion); // check whether we are now using a mutation type that is non-neutral; check and set pure_neutral_ - if (!mutation_type_ptr->IsPureNeutralDES()) - //if ((mutation_type_ptr->des_type_ != DESType::kFixed) || (mutation_type_ptr->des_parameters_[0] != 0.0)) + if (!mutation_type_ptr->all_neutral_DES_) species_.pure_neutral_ = false; } diff --git a/core/haplosome.cpp b/core/haplosome.cpp index e3d67b56..f7b3c662 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2572,7 +2572,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addMutations(EidosGlobalStringID p_ target_run->insert_sorted_mutation_if_unique(mut_block_ptr, mutation_block->IndexInBlock(mut_to_add)); // No need to add the mutation to the registry; how would the user ever get a Mutation that was not already in it? - // Similarly, no need to check and set pure_neutral_ and all_pure_neutral_DES_; the mutation is already in the system + // Similarly, no need to check and set pure_neutral_ and all_neutral_mutations_; the mutation is already in the system } } } @@ -2953,7 +2953,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, origin_subpop_id, origin_tick, (int8_t)nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - if (!mutation_type_ptr->all_pure_neutral_DES_) + if (!mutation_type_ptr->all_neutral_DES_) species->pure_neutral_ = false; } else // (p_method_id == gID_addNewMutation) @@ -2973,11 +2973,11 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - // The selection coefficient was supplied by the user (i.e., not be from the mutation type's DES), so we set all_pure_neutral_DES_ also + // The selection coefficient was supplied by the user (i.e., not be from the mutation type's DES), so we set all_neutral_mutations_ also if (selection_coeff != 0.0) { species->pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DES_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; } } @@ -3480,8 +3480,8 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr { species.pure_neutral_ = false; - // the selection coefficient was drawn from the mutation type's DES, so there is no need to set all_pure_neutral_DES_ - //mutation_type_ptr->all_pure_neutral_DES_ = false; + // the selection coefficient was drawn from the mutation type's DES, so there is no need to set all_neutral_mutations_ + //mutation_type_ptr->all_neutral_mutations_ = false; } // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry @@ -4133,11 +4133,11 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt } // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DES), so we set all_pure_neutral_DES_ also + // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DES), so we set all_neutral_mutations_ also if (selection_coeff != 0.0) { species->pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DES_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; } // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry diff --git a/core/individual.cpp b/core/individual.cpp index eac7cc21..40732469 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -5387,7 +5387,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (selection_coeff != 0.0) { species->pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DES_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; } // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry diff --git a/core/mutation.cpp b/core/mutation.cpp index 6d30110c..f75a0689 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -91,7 +91,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ is_neutral_ = false; species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation - mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + mutation_type_ptr_->all_neutral_mutations_ = false; // let the mutation type for this mutation know that it is no longer neutral species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! // get the realized dominance to handle the possibility of independent dominance @@ -170,7 +170,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits is_independent_dominance_ = std::isnan(mutation_type_ptr_->DefaultDominanceForTrait(0)); - if (mutation_type_ptr_->all_pure_neutral_DES_) + if (mutation_type_ptr_->all_neutral_DES_) { // The DES of the mutation type is pure neutral, so we don't need to do any draws; we can short-circuit // most of the work here and just set up neutral effects for all of the traits. @@ -318,7 +318,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ is_neutral_ = false; species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation - mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + mutation_type_ptr_->all_neutral_mutations_ = false; // let the mutation type for this mutation know that it is no longer pure neutral species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! // get the realized dominance to handle the possibility of independent dominance @@ -494,7 +494,7 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e Species &species = mutation_type_ptr_->species_; species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation - mutation_type_ptr_->all_pure_neutral_DES_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + mutation_type_ptr_->all_neutral_mutations_ = false; // let the mutation type for this mutation know that it is no longer pure neutral species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags } @@ -541,7 +541,7 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e } } - // Note that we cannot set species.pure_neutral_ and mutation_type_ptr_->all_pure_neutral_DES_ to + // Note that we cannot set species.pure_neutral_ and mutation_type_ptr_->all_neutral_mutations_ to // false here, because only this mutation has changed to neutral; other mutations might be non-neutral species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags @@ -1365,13 +1365,8 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth mutation_type_ptr_ = mutation_type_ptr; // If we are non-neutral, make sure the mutation type knows it is now also non-neutral - // FIXME MULTITRAIT: I think it might be useful for MutationType to keep a flag separately for each trait, whether *that* trait is all_pure_neutral_DES_ or not - //int trait_count = species.TraitCount(); - //MutationBlock *mutation_block = species.SpeciesMutationBlock(); - //MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); - if (!is_neutral_) - mutation_type_ptr_->all_pure_neutral_DES_ = false; + mutation_type_ptr_->all_neutral_mutations_ = false; // Changing the mutation type no longer changes the dominance coefficient or the hemizygous dominance // coefficient, so there are no longer any side effects on trait effects / fitness to be managed here. diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 622c1d65..33d7fc87 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -82,10 +82,13 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr // intentionally no bounds checks for DES parameters; the count of DES parameters is checked prior to construction // intentionally no bounds check for dominance_coeff_ - // determine whether this mutation type is initially pure neutral; note that this flag will be - // cleared if any mutation of this type has its effect changed - // note also that we do not set Species.pure_neutral_ here; we wait until this muttype is used - all_pure_neutral_DES_ = ((p_DES_type == DESType::kFixed) && (p_DES_parameters[0] == 0.0)); + // determine whether this mutation type has a neutral DES + // note that we do not set Species.pure_neutral_ here; we wait until this muttype is used + all_neutral_DES_ = ((p_DES_type == DESType::kFixed) && (p_DES_parameters[0] == 0.0)); + + // initially, whether a mutation type has any neutral mutations is inherited from whether it has a neutral DES + // note that this flag will be cleared if any mutation of this type has its effect changed to non-neutral + all_neutral_mutations_ = all_neutral_DES_; // set up DE entries for all traits; every trait is initialized identically, from the parameters given EffectDistributionInfo DES_info; @@ -1029,11 +1032,23 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo // mark that mutation types changed, so they get redisplayed in SLiMgui species_.community_.mutation_types_changed_ = true; - // check whether we are now using a DES type that is non-neutral; check and set pure_neutral_ and all_pure_neutral_DES_ - if ((DES_type != DESType::kFixed) || (DES_parameters[0] != 0.0)) + // check whether our DES for all traits is now neutral; we can change from non-neutral back to neutral + all_neutral_DES_ = true; + + for (int64_t trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) + { + EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + + if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) + all_neutral_DES_ = false; + } + + // if our DES is non-neutral, set pure_neutral_ and all_neutral_mutations_ to false; + // these flags are sticky, so we don't try to set them back to true again + if (!all_neutral_DES_) { species_.pure_neutral_ = false; - all_pure_neutral_DES_ = false; + all_neutral_mutations_ = false; } return gStaticEidosValueVOID; diff --git a/core/mutation_type.h b/core/mutation_type.h index f58afacb..d43dcb96 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -129,18 +129,26 @@ class MutationType : public EidosDictionaryUnretained MutationRun muttype_registry_; #endif - // For optimizing the fitness calculation code, the exact situation for each mutation type is of great interest: does it have - // a neutral DES, and if so has any mutation of that type had its selection coefficient changed to be non-zero, are mutations - // of this type made neutral by a constant callback like "return 1.0;", and so forth. Different parts of the code need to - // know slightly different things, so we have several different flags of this sort. + // For optimizing phenotype calculations, the exact situation for each mutation type is of great interest: + // does it have a neutral DES, and if so has any mutation of that type had its selection coefficient changed + // to be non-zero, are mutations of this type made neutral by a constant callback like "return 1.0;", and so + // forth. Different parts of the code need to know slightly different things, so we have several different + // flags of this sort. The subtle differences between these flags can be crucially important! - // all_pure_neutral_DES_ is true if the DES is "f" 0.0. It is cleared if any mutation of this type has its selection coefficient - // changed, so it can be used as a reliable indicator that mutations of a given mutation type are actually neutral – except for - // the effects of mutationEffect() callbacks, which might make them non-neutral in a given tick / subpopulation. - mutable bool all_pure_neutral_DES_; + // all_neutral_DES_ is true if and only if the DES for all traits is "f" 0.0. Mutations of this type + // could still be non-neutral (because they were changed, or created at a time when the DES was not neutral), + // and callbacks could still change mutation effects. What this flag does tell you is that if a new mutation + // of this type is being created now, it will be configured to be neutral. This flag is not "sticky"; it will + // change back from false to true if the DES changes from non-neutral back to neutral. + mutable bool all_neutral_DES_; + + // all_pure_neutral_mutations_ is true if any mutation of this type could be non-neutral. That is the case + // if (a) the mutation type has ever had a non-neutral DES, or (b) if any mutation of this type has ever been + // configured to be non-neutral. This flag is "sticky"; once set to true it will remain true forever. + mutable bool all_neutral_mutations_; // is_pure_neutral_now_ is set up by Subpopulation::UpdateFitness(), and is valid only inside a given UpdateFitness() call. - // If set, it indicates that the mutation type is currently pure neutral – either because all_pure_neutral_DES_ is set and the + // If set, it indicates that the mutation type is currently pure neutral – either because all_neutral_DES_ is set and the // mutation type cannot be influenced by any callbacks in the current subpopulation / tick, or because an active callback // actually sets the mutation type to be a constant value of 1.0 in this subpopulation / tick. Mutations for which this // flag is set can be safely elided from fitness calculations altogether; the flag will not be set if other active callbacks @@ -200,7 +208,6 @@ class MutationType : public EidosDictionaryUnretained slim_effect_t DrawEffectForTrait(int64_t p_trait_index) const; // draw a selection coefficient from the DE for a trait - bool IsPureNeutralDES(void) const { return all_pure_neutral_DES_; } // // Eidos support diff --git a/core/population.cpp b/core/population.cpp index 2b752525..9bbf928d 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -3166,7 +3166,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // no need to worry about pure_neutral_ or all_pure_neutral_DES_ here; the mutation is drawn from a registered genomic element type + // no need to worry about pure_neutral_ or all_neutral_mutations_ here; the mutation is drawn from a registered genomic element type // we can't handle the stacking policy here, since we don't yet know what the context of the new mutation will be; we do it below // we add the new mutation to the registry below, if the stacking policy says the mutation can actually be added } @@ -3808,7 +3808,7 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // no need to worry about pure_neutral_ or all_pure_neutral_DES_ here; the mutation is drawn from a registered genomic element type + // no need to worry about pure_neutral_ or all_neutral_mutations_ here; the mutation is drawn from a registered genomic element type // we can't handle the stacking policy here, since we don't yet know what the context of the new mutation will be; we do it below // we add the new mutation to the registry below, if the stacking policy says the mutation can actually be added } @@ -4210,7 +4210,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // no need to worry about pure_neutral_ or all_pure_neutral_DES_ here; the mutation is drawn from a registered genomic element type + // no need to worry about pure_neutral_ or all_neutral_mutations_ here; the mutation is drawn from a registered genomic element type // we can't handle the stacking policy here, since we don't yet know what the context of the new mutation will be; we do it below // we add the new mutation to the registry below, if the stacking policy says the mutation can actually be added } diff --git a/core/species.cpp b/core/species.cpp index 3b15a817..af50f2ae 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1418,11 +1418,11 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromTextFile): (internal error) separate muttype registries set up during pop load." << EidosTerminate(); #endif - // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_pure_neutral_DES_ + // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_neutral_mutations_ if (selection_coeff != 0.0) { pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DES_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; } } @@ -2184,11 +2184,11 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): (internal error) separate muttype registries set up during pop load." << EidosTerminate(); #endif - // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_pure_neutral_DES_ + // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_neutral_mutations_ if (selection_coeff != 0.0) { pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DES_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; } } @@ -2808,7 +2808,7 @@ void Species::RunInitializeCallbacks(void) for (auto muttype : getype->mutation_type_ptrs_) { - if (muttype->IsPureNeutralDES()) + if (muttype->all_neutral_DES_) using_neutral_muttype = true; } } @@ -9946,11 +9946,11 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapall_pure_neutral_DES_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; } } } diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index cdcf52e1..2a6113d2 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -529,7 +529,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const mutation_fractions.emplace_back(proportion); // check whether we are using a mutation type that is non-neutral; check and set pure_neutral_ - if (!mutation_type_ptr->IsPureNeutralDES()) + if (!mutation_type_ptr->all_neutral_DES_) pure_neutral_ = false; } diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 413e147e..7a3d630a 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1470,9 +1470,12 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect // by mutationEffect() callbacks. Note this block is the only place where is_pure_neutral_now_ is valid or used!!! if (skip_chromosomal_fitness) { - // first set a flag on all mut types indicating whether they are pure neutral according to their DES + // first set a flag on all mut types indicating whether they are neutral according to their mutations + // this is the one place where all_neutral_mutations_ is actually used in the current design, because + // mutationEffect() callbacks target mutation types -- we want to know if all of the non-neutral + // mutation types have been made neutral by a mutationEffect() callback. for (auto &mut_type_iter : mut_types) - mut_type_iter.second->is_pure_neutral_now_ = mut_type_iter.second->all_pure_neutral_DES_; + mut_type_iter.second->is_pure_neutral_now_ = mut_type_iter.second->all_neutral_mutations_; // then go through the mutationEffect() callback list and set the pure neutral flag for mut types neutralized by an active callback for (SLiMEidosBlock *mutationEffect_callback : p_mutationEffect_callbacks) From d97f205b57df451ed94bddc5511b239f37d72b92 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 29 Dec 2025 09:36:27 -0600 Subject: [PATCH 047/107] comment out nonneutral cache usage --- core/individual.cpp | 51 ++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/core/individual.cpp b/core/individual.cpp index 40732469..db8cb2cd 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -6123,11 +6123,6 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual // "independent dominance". #warning recache non-neutral caches first - // FIXME MULTITRAIT BCH 12/25/2025: For now we disable the non-neutral caches. To enable them we'd need to - // deal with the "regime" stuff that Population::RecalculateFitness() does, and I think that probably all - // needs to get redesigned, so I'm not going to try to get it working here for now. -#define SLIM_USE_NONNEUTRAL_CACHES 0 - // For a given individual, for a given trait, we have to make a decision as to whether we will recalculate or not. That decision gets made // once and then holds across all chromosomes for the individual. But we're looping over chromosomes at the topmost level, so we have a // little problem: how will we remember whether we decided to recalculate a given individual/trait when we get to doing the work for @@ -6406,10 +6401,10 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos // we just need to scan through the haplosome and account for its mutations, using the homozygous mutation // effect (no dominance effects with haploidy), or the hemizygous mutation effect for f_hemizygous == true -#if SLIM_USE_NONNEUTRAL_CACHES - int32_t nonneutral_change_counter = species->nonneutral_change_counter_; - int32_t nonneutral_regime = species->last_nonneutral_regime_; -#endif +//#if SLIM_USE_NONNEUTRAL_CACHES +// int32_t nonneutral_change_counter = species->nonneutral_change_counter_; +// int32_t nonneutral_regime = species->last_nonneutral_regime_; +//#endif // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast MutationType *single_callback_mut_type; @@ -6431,16 +6426,16 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos { const MutationRun *mutrun = haplosome->mutruns_[run_index]; -#if SLIM_USE_NONNEUTRAL_CACHES - // Cache non-neutral mutations and read from the non-neutral buffers - const MutationIndex *haplosome_iter, *haplosome_max; - - mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); -#else +//#if SLIM_USE_NONNEUTRAL_CACHES +// // Cache non-neutral mutations and read from the non-neutral buffers +// const MutationIndex *haplosome_iter, *haplosome_max; +// +// mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); +//#else // Read directly from the MutationRun buffers const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); const MutationIndex *haplosome_max = mutrun->end_pointer_const(); -#endif +//#endif // scan the mutation run and apply mutation effects while (haplosome_iter != haplosome_max) @@ -6504,10 +6499,10 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos // both haplosomes are non-null, so we need to scan through and figure out which mutations are // heterozygous and which are homozygous, and assign effects accordingly -#if SLIM_USE_NONNEUTRAL_CACHES - int32_t nonneutral_change_counter = species->nonneutral_change_counter_; - int32_t nonneutral_regime = species->last_nonneutral_regime_; -#endif +//#if SLIM_USE_NONNEUTRAL_CACHES +// int32_t nonneutral_change_counter = species->nonneutral_change_counter_; +// int32_t nonneutral_regime = species->last_nonneutral_regime_; +//#endif // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast MutationType *single_callback_mut_type; @@ -6530,20 +6525,20 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos const MutationRun *mutrun1 = haplosome1->mutruns_[run_index]; const MutationRun *mutrun2 = haplosome2->mutruns_[run_index]; -#if SLIM_USE_NONNEUTRAL_CACHES - // Cache non-neutral mutations and read from the non-neutral buffers - const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; - - mutrun1->beginend_nonneutral_pointers(mut_block_ptr, &haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); - mutrun2->beginend_nonneutral_pointers(mut_block_ptr, &haplosome2_iter, &haplosome2_max, nonneutral_change_counter, nonneutral_regime); -#else +//#if SLIM_USE_NONNEUTRAL_CACHES +// // Cache non-neutral mutations and read from the non-neutral buffers +// const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; +// +// mutrun1->beginend_nonneutral_pointers(mut_block_ptr, &haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); +// mutrun2->beginend_nonneutral_pointers(mut_block_ptr, &haplosome2_iter, &haplosome2_max, nonneutral_change_counter, nonneutral_regime); +//#else // Read directly from the MutationRun buffers const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); const MutationIndex *haplosome2_iter = mutrun2->begin_pointer_const(); const MutationIndex *haplosome1_max = mutrun1->end_pointer_const(); const MutationIndex *haplosome2_max = mutrun2->end_pointer_const(); -#endif +//#endif // first, handle the situation before either haplosome iterator has reached the end of its haplosome, for simplicity/speed if ((haplosome1_iter != haplosome1_max) && (haplosome2_iter != haplosome2_max)) From 2861078c99df88665efe127c96bfc453689d4d71 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 29 Dec 2025 10:42:57 -0600 Subject: [PATCH 048/107] fix class objects to have the correct type --- QtSLiM/QtSLiM_Plot.cpp | 2 +- QtSLiM/QtSLiM_Plot.h | 3 ++- QtSLiM/QtSLiM_SLiMgui.cpp | 2 +- QtSLiM/QtSLiM_SLiMgui.h | 3 ++- SLiMgui/plot.h | 3 ++- SLiMgui/plot.mm | 2 +- SLiMgui/slim_gui.h | 3 ++- SLiMgui/slim_gui.mm | 2 +- core/chromosome.cpp | 2 +- core/chromosome.h | 3 ++- core/community.h | 5 +++-- core/community_eidos.cpp | 2 +- core/genomic_element.cpp | 2 +- core/genomic_element.h | 3 ++- core/genomic_element_type.cpp | 2 +- core/genomic_element_type.h | 3 ++- core/haplosome.cpp | 2 +- core/haplosome.h | 4 ++-- core/individual.cpp | 2 +- core/individual.h | 6 ++++-- core/interaction_type.cpp | 2 +- core/interaction_type.h | 5 ++--- core/log_file.cpp | 2 +- core/log_file.h | 4 +++- core/mutation.cpp | 2 +- core/mutation.h | 4 +++- core/mutation_type.cpp | 2 +- core/mutation_type.h | 3 ++- core/slim_eidos_block.cpp | 2 +- core/slim_eidos_block.h | 8 +++++--- core/spatial_map.cpp | 2 +- core/spatial_map.h | 7 +++++-- core/species.h | 6 ++++-- core/species_eidos.cpp | 2 +- core/subpopulation.cpp | 2 +- core/subpopulation.h | 5 +++-- core/substitution.cpp | 2 +- core/substitution.h | 4 +++- core/trait.cpp | 2 +- core/trait.h | 3 ++- eidos/eidos_class_DataFrame.cpp | 2 +- eidos/eidos_class_DataFrame.h | 3 ++- eidos/eidos_class_Dictionary.cpp | 4 ++-- eidos/eidos_class_Dictionary.h | 11 +++++++---- eidos/eidos_class_Image.cpp | 2 +- eidos/eidos_class_Image.h | 4 +++- eidos/eidos_class_TestElement.cpp | 4 ++-- eidos/eidos_class_TestElement.h | 14 ++++++++------ 48 files changed, 101 insertions(+), 68 deletions(-) diff --git a/QtSLiM/QtSLiM_Plot.cpp b/QtSLiM/QtSLiM_Plot.cpp index 34071fb1..0caa895e 100644 --- a/QtSLiM/QtSLiM_Plot.cpp +++ b/QtSLiM/QtSLiM_Plot.cpp @@ -1857,7 +1857,7 @@ EidosValue_SP Plot::ExecuteMethod_write(EidosGlobalStringID p_method_id, const s #pragma mark Plot_Class #pragma mark - -EidosClass *gSLiM_Plot_Class = nullptr; +Plot_Class *gSLiM_Plot_Class = nullptr; const std::vector *Plot_Class::Properties(void) const diff --git a/QtSLiM/QtSLiM_Plot.h b/QtSLiM/QtSLiM_Plot.h index 6197067e..88a5305f 100644 --- a/QtSLiM/QtSLiM_Plot.h +++ b/QtSLiM/QtSLiM_Plot.h @@ -30,7 +30,8 @@ class QtSLiMGraphView_CustomPlot; -extern EidosClass *gSLiM_Plot_Class; +class Plot_Class; +extern Plot_Class *gSLiM_Plot_Class; class Plot : public EidosDictionaryUnretained diff --git a/QtSLiM/QtSLiM_SLiMgui.cpp b/QtSLiM/QtSLiM_SLiMgui.cpp index 9ef91a6e..4c0d1c98 100644 --- a/QtSLiM/QtSLiM_SLiMgui.cpp +++ b/QtSLiM/QtSLiM_SLiMgui.cpp @@ -296,7 +296,7 @@ EidosValue_SP SLiMgui::ExecuteMethod_plotWithTitle(EidosGlobalStringID p_method_ #pragma mark SLiMgui_Class #pragma mark - -EidosClass *gSLiM_SLiMgui_Class = nullptr; +SLiMgui_Class *gSLiM_SLiMgui_Class = nullptr; const std::vector *SLiMgui_Class::Properties(void) const diff --git a/QtSLiM/QtSLiM_SLiMgui.h b/QtSLiM/QtSLiM_SLiMgui.h index a2ed1891..23cff744 100644 --- a/QtSLiM/QtSLiM_SLiMgui.h +++ b/QtSLiM/QtSLiM_SLiMgui.h @@ -32,7 +32,8 @@ class QtSLiMWindow; -extern EidosClass *gSLiM_SLiMgui_Class; +class SLiMgui_Class; +extern SLiMgui_Class *gSLiM_SLiMgui_Class; class SLiMgui : public EidosDictionaryUnretained diff --git a/SLiMgui/plot.h b/SLiMgui/plot.h index 7b3c907c..5fb7aaa7 100644 --- a/SLiMgui/plot.h +++ b/SLiMgui/plot.h @@ -37,7 +37,8 @@ @class SLiMWindowController; -extern EidosClass *gSLiM_Plot_Class; +class Plot_Class; +extern Plot_Class *gSLiM_Plot_Class; class Plot : public EidosDictionaryUnretained diff --git a/SLiMgui/plot.mm b/SLiMgui/plot.mm index 418b1088..26ebb1e5 100644 --- a/SLiMgui/plot.mm +++ b/SLiMgui/plot.mm @@ -276,7 +276,7 @@ #pragma mark Plot_Class #pragma mark - -EidosClass *gSLiM_Plot_Class = nullptr; +Plot_Class *gSLiM_Plot_Class = nullptr; const std::vector *Plot_Class::Properties(void) const diff --git a/SLiMgui/slim_gui.h b/SLiMgui/slim_gui.h index 89f9f403..e30ca09d 100644 --- a/SLiMgui/slim_gui.h +++ b/SLiMgui/slim_gui.h @@ -38,7 +38,8 @@ @class SLiMWindowController; -extern EidosClass *gSLiM_SLiMgui_Class; +class SLiMgui_Class; +extern SLiMgui_Class *gSLiM_SLiMgui_Class; class SLiMgui : public EidosDictionaryUnretained diff --git a/SLiMgui/slim_gui.mm b/SLiMgui/slim_gui.mm index fb415686..d5beede5 100644 --- a/SLiMgui/slim_gui.mm +++ b/SLiMgui/slim_gui.mm @@ -195,7 +195,7 @@ #pragma mark SLiMgui_Class #pragma mark - -EidosClass *gSLiM_SLiMgui_Class = nullptr; +SLiMgui_Class *gSLiM_SLiMgui_Class = nullptr; const std::vector *SLiMgui_Class::Properties(void) const diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 3e54bada..6241a77e 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -3753,7 +3753,7 @@ EidosValue_SP Chromosome::ExecuteMethod_setRecombinationRate(EidosGlobalStringID #pragma mark Chromosome_Class #pragma mark - -EidosClass *gSLiM_Chromosome_Class = nullptr; +Chromosome_Class *gSLiM_Chromosome_Class = nullptr; const std::vector *Chromosome_Class::Properties(void) const diff --git a/core/chromosome.h b/core/chromosome.h index 044d3e92..8e102fe7 100644 --- a/core/chromosome.h +++ b/core/chromosome.h @@ -45,7 +45,8 @@ class Species; class Individual; -extern EidosClass *gSLiM_Chromosome_Class; +class Chromosome_Class; +extern Chromosome_Class *gSLiM_Chromosome_Class; class Chromosome : public EidosDictionaryRetained diff --git a/core/community.h b/core/community.h index e1e6fe64..3cd99448 100644 --- a/core/community.h +++ b/core/community.h @@ -38,7 +38,6 @@ #include "eidos_functions.h" #include "slim_eidos_block.h" - class EidosInterpreter; class Individual; class LogFile; @@ -49,7 +48,9 @@ struct EidosInterpreterDebugPointsSet_struct; typedef EidosInterpreterDebugPointsSet_struct EidosInterpreterDebugPointsSet; #endif -extern EidosClass *gSLiM_Community_Class; + +class Community_Class; +extern Community_Class *gSLiM_Community_Class; #pragma mark - diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index 269a5035..c771048b 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -1346,7 +1346,7 @@ EidosValue_SP Community::ExecuteMethod_usage(EidosGlobalStringID p_method_id, co #pragma mark Community_Class #pragma mark - -EidosClass *gSLiM_Community_Class = nullptr; +Community_Class *gSLiM_Community_Class = nullptr; const std::vector *Community_Class::Properties(void) const diff --git a/core/genomic_element.cpp b/core/genomic_element.cpp index bec96162..f2f217da 100644 --- a/core/genomic_element.cpp +++ b/core/genomic_element.cpp @@ -215,7 +215,7 @@ EidosValue_SP GenomicElement::ExecuteMethod_setGenomicElementType(EidosGlobalStr #pragma mark GenomicElement_Class #pragma mark - -EidosClass *gSLiM_GenomicElement_Class = nullptr; +GenomicElement_Class *gSLiM_GenomicElement_Class = nullptr; const std::vector *GenomicElement_Class::Properties(void) const diff --git a/core/genomic_element.h b/core/genomic_element.h index 8b67e05a..9eef739d 100644 --- a/core/genomic_element.h +++ b/core/genomic_element.h @@ -34,7 +34,8 @@ #include "eidos_value.h" -extern EidosClass *gSLiM_GenomicElement_Class; +class GenomicElement_Class; +extern GenomicElement_Class *gSLiM_GenomicElement_Class; class GenomicElement : public EidosObject diff --git a/core/genomic_element_type.cpp b/core/genomic_element_type.cpp index 2615533f..c5a4a124 100644 --- a/core/genomic_element_type.cpp +++ b/core/genomic_element_type.cpp @@ -450,7 +450,7 @@ EidosValue_SP GenomicElementType::ExecuteMethod_setMutationMatrix(EidosGlobalStr #pragma mark GenomicElementType_Class #pragma mark - -EidosClass *gSLiM_GenomicElementType_Class = nullptr; +GenomicElementType_Class *gSLiM_GenomicElementType_Class = nullptr; const std::vector *GenomicElementType_Class::Properties(void) const diff --git a/core/genomic_element_type.h b/core/genomic_element_type.h index 4c2300a1..a478f166 100644 --- a/core/genomic_element_type.h +++ b/core/genomic_element_type.h @@ -41,7 +41,8 @@ class Species; -extern EidosClass *gSLiM_GenomicElementType_Class; +class GenomicElementType_Class; +extern GenomicElementType_Class *gSLiM_GenomicElementType_Class; class GenomicElementType : public EidosDictionaryUnretained diff --git a/core/haplosome.cpp b/core/haplosome.cpp index f7b3c662..16bc8c96 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2227,7 +2227,7 @@ size_t Haplosome::MemoryUsageForMutrunBuffers(void) #pragma mark Haplosome_Class #pragma mark - -EidosClass *gSLiM_Haplosome_Class = nullptr; +Haplosome_Class *gSLiM_Haplosome_Class = nullptr; const std::vector *Haplosome_Class::Properties(void) const diff --git a/core/haplosome.h b/core/haplosome.h index 7df2b7f2..7bc7801d 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -56,7 +56,6 @@ typedef std::unordered_map SLiMBulkOpera typedef std::pair SLiMBulkOperationPair; #endif - class Species; class Population; class Subpopulation; @@ -65,7 +64,8 @@ class HaplosomeWalker; class MutationBlock; -extern EidosClass *gSLiM_Haplosome_Class; +class Haplosome_Class; +extern Haplosome_Class *gSLiM_Haplosome_Class; // Haplosome now keeps an array of MutationRun objects, and those objects actually hold the mutations of the haplosome. This design diff --git a/core/individual.cpp b/core/individual.cpp index db8cb2cd..2468cf29 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -4195,7 +4195,7 @@ EidosValue_SP Individual::ExecuteMethod_mutationsFromHaplosomes(EidosGlobalStrin #pragma mark Individual_Class #pragma mark - -EidosClass *gSLiM_Individual_Class = nullptr; +Individual_Class *gSLiM_Individual_Class = nullptr; const std::vector *Individual_Class::Properties(void) const diff --git a/core/individual.h b/core/individual.h index 8c3fbc98..00743d60 100644 --- a/core/individual.h +++ b/core/individual.h @@ -39,10 +39,12 @@ #include "haplosome.h" - class Subpopulation; -extern EidosClass *gSLiM_Individual_Class; + +class Individual_Class; +extern Individual_Class *gSLiM_Individual_Class; + // A global counter used to assign all Individual objects a unique ID. Note this is shared by all species. extern slim_pedigreeid_t gSLiM_next_pedigree_id; // use SLiM_GetNextPedigreeID() instead, for THREAD_SAFETY_IN_ACTIVE_PARALLEL() diff --git a/core/interaction_type.cpp b/core/interaction_type.cpp index bdd0f30e..950568fa 100755 --- a/core/interaction_type.cpp +++ b/core/interaction_type.cpp @@ -6508,7 +6508,7 @@ EidosValue_SP InteractionType::ExecuteMethod_unevaluate(EidosGlobalStringID p_me #pragma mark InteractionType_Class #pragma mark - -EidosClass *gSLiM_InteractionType_Class = nullptr; +InteractionType_Class *gSLiM_InteractionType_Class = nullptr; const std::vector *InteractionType_Class::Properties(void) const diff --git a/core/interaction_type.h b/core/interaction_type.h index 5d6de121..5dce963e 100644 --- a/core/interaction_type.h +++ b/core/interaction_type.h @@ -42,14 +42,13 @@ #include "subpopulation.h" #include "spatial_kernel.h" - class Species; class Subpopulation; class Individual; -class InteractionType_Class; -extern EidosClass *gSLiM_InteractionType_Class; +class InteractionType_Class; +extern InteractionType_Class *gSLiM_InteractionType_Class; // This class uses an internal implementation of kd-trees for fast nearest-neighbor finding. We use the same data structure to diff --git a/core/log_file.cpp b/core/log_file.cpp index d11845b1..74bc6ab8 100644 --- a/core/log_file.cpp +++ b/core/log_file.cpp @@ -1132,7 +1132,7 @@ EidosValue_SP LogFile::ExecuteMethod_setValue(EidosGlobalStringID p_method_id, c #pragma mark LogFile_Class #pragma mark - -EidosClass *gSLiM_LogFile_Class = nullptr; +LogFile_Class *gSLiM_LogFile_Class = nullptr; const std::vector *LogFile_Class::Properties(void) const diff --git a/core/log_file.h b/core/log_file.h index ac8e0b0a..b88218a0 100644 --- a/core/log_file.h +++ b/core/log_file.h @@ -20,6 +20,7 @@ #ifndef log_file_h #define log_file_h + #include "eidos_value.h" #include "slim_globals.h" @@ -29,7 +30,8 @@ class Community; -extern EidosClass *gSLiM_LogFile_Class; +class LogFile_Class; +extern LogFile_Class *gSLiM_LogFile_Class; // Built-in and custom generator types that are presently supported diff --git a/core/mutation.cpp b/core/mutation.cpp index f75a0689..b92811bd 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -1382,7 +1382,7 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth #pragma mark Mutation_Class #pragma mark - -EidosClass *gSLiM_Mutation_Class = nullptr; +Mutation_Class *gSLiM_Mutation_Class = nullptr; const std::vector *Mutation_Class::Properties(void) const diff --git a/core/mutation.h b/core/mutation.h index d22a4a04..b7935e91 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -37,7 +37,9 @@ class MutationType; class Trait; -extern EidosClass *gSLiM_Mutation_Class; +class Mutation_Class; +extern Mutation_Class *gSLiM_Mutation_Class; + // A global counter used to assign all Mutation objects a unique ID extern slim_mutationid_t gSLiM_next_mutation_id; diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 33d7fc87..f3ce9868 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -1062,7 +1062,7 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo #pragma mark MutationType_Class #pragma mark - -EidosClass *gSLiM_MutationType_Class = nullptr; +MutationType_Class *gSLiM_MutationType_Class = nullptr; const std::vector *MutationType_Class::Properties(void) const diff --git a/core/mutation_type.h b/core/mutation_type.h index d43dcb96..8e71bf25 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -44,7 +44,8 @@ class Species; class MutationBlock; -extern EidosClass *gSLiM_MutationType_Class; +class MutationType_Class; +extern MutationType_Class *gSLiM_MutationType_Class; // This enumeration represents a type of distribution of effect sizes (DES) that a mutation type can draw from diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index 555cb451..7f5a9405 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -1781,7 +1781,7 @@ void SLiMEidosBlock::SetProperty(EidosGlobalStringID p_property_id, const EidosV #pragma mark SLiMEidosBlock_Class #pragma mark - -EidosClass *gSLiM_SLiMEidosBlock_Class = nullptr; +SLiMEidosBlock_Class *gSLiM_SLiMEidosBlock_Class = nullptr; const std::vector *SLiMEidosBlock_Class::Properties(void) const diff --git a/core/slim_eidos_block.h b/core/slim_eidos_block.h index 6b9be7be..177c9edf 100644 --- a/core/slim_eidos_block.h +++ b/core/slim_eidos_block.h @@ -28,6 +28,7 @@ #ifndef __SLiM__slim_script_block__ #define __SLiM__slim_script_block__ + #include "slim_globals.h" #include "eidos_script.h" #include "eidos_value.h" @@ -40,6 +41,10 @@ class Community; +class SLiMEidosBlock_Class; +extern SLiMEidosBlock_Class *gSLiM_SLiMEidosBlock_Class; + + enum class SLiMEidosBlockType { SLiMEidosEventFirst = 0, SLiMEidosEventEarly, @@ -112,9 +117,6 @@ class SLiMEidosScript : public EidosScript #pragma mark SLiMEidosBlock #pragma mark - -extern EidosClass *gSLiM_SLiMEidosBlock_Class; - - class SLiMEidosBlock : public EidosDictionaryUnretained { // This class has its copy constructor and assignment operator disabled, to prevent accidental copying. diff --git a/core/spatial_map.cpp b/core/spatial_map.cpp index ccdeb43c..22e25fab 100644 --- a/core/spatial_map.cpp +++ b/core/spatial_map.cpp @@ -3173,7 +3173,7 @@ static EidosValue_SP SLiM_Instantiate_SpatialMap(const std::vector *SpatialMap_Class::Properties(void) const diff --git a/core/spatial_map.h b/core/spatial_map.h index 34f45637..bb031219 100644 --- a/core/spatial_map.h +++ b/core/spatial_map.h @@ -30,6 +30,7 @@ #ifndef __SLiM__spatial_map__ #define __SLiM__spatial_map__ + #include "slim_globals.h" #include "eidos_value.h" #include "eidos_symbol_table.h" @@ -39,12 +40,14 @@ class Subpopulation; class SpatialKernel; +class SpatialMap_Class; +extern SpatialMap_Class *gSLiM_SpatialMap_Class; + + #pragma mark - #pragma mark SpatialMap #pragma mark - -extern EidosClass *gSLiM_SpatialMap_Class; - class SpatialMap : public EidosDictionaryRetained { // This class has its copy constructor and assignment operator disabled, to prevent accidental copying. diff --git a/core/species.h b/core/species.h index 397de0df..b78d0e51 100644 --- a/core/species.h +++ b/core/species.h @@ -51,7 +51,6 @@ extern "C" { } #endif - class Community; class EidosInterpreter; class Individual; @@ -62,7 +61,10 @@ class InteractionType; struct ts_subpop_info; struct ts_mut_info; -extern EidosClass *gSLiM_Species_Class; + +class Species_Class; +extern Species_Class *gSLiM_Species_Class; + enum class SLiMFileFormat { diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 2a6113d2..fa3adf4a 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -4694,7 +4694,7 @@ EidosValue_SP Species::ExecuteMethod__debug(EidosGlobalStringID p_method_id, con #pragma mark Species_Class #pragma mark - -EidosClass *gSLiM_Species_Class = nullptr; +Species_Class *gSLiM_Species_Class = nullptr; const std::vector *Species_Class::Properties(void) const diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 7a3d630a..0e345858 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -12737,7 +12737,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_configureDisplay(EidosGlobalStringID #pragma mark Subpopulation_Class #pragma mark - -EidosClass *gSLiM_Subpopulation_Class = nullptr; +Subpopulation_Class *gSLiM_Subpopulation_Class = nullptr; const std::vector *Subpopulation_Class::Properties(void) const diff --git a/core/subpopulation.h b/core/subpopulation.h index 1ce64abb..a41fe08f 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -66,11 +66,12 @@ #include #include - class Population; -extern EidosClass *gSLiM_Subpopulation_Class; +class Subpopulation_Class; +extern Subpopulation_Class *gSLiM_Subpopulation_Class; + typedef std::pair SpatialMapPair; typedef std::map SpatialMapMap; diff --git a/core/substitution.cpp b/core/substitution.cpp index 19ef842f..f1a9304b 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -816,7 +816,7 @@ EidosValue_SP Substitution::ExecuteMethod_hemizygousDominanceForTrait(EidosGloba #pragma mark Substitution_Class #pragma mark - -EidosClass *gSLiM_Substitution_Class = nullptr; +Substitution_Class *gSLiM_Substitution_Class = nullptr; const std::vector *Substitution_Class::Properties(void) const diff --git a/core/substitution.h b/core/substitution.h index baa74842..8b9d6288 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -37,7 +37,9 @@ class Trait; -extern EidosClass *gSLiM_Substitution_Class; +class Substitution_Class; +extern Substitution_Class *gSLiM_Substitution_Class; + // This structure contains all of the information about how a substitution influenced a particular trait: in particular, its // effect size and dominance coefficient. Each substitution keeps this information for each trait in its species, and since diff --git a/core/trait.cpp b/core/trait.cpp index 472c9b01..9971b42d 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -238,7 +238,7 @@ EidosValue_SP Trait::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, cons #pragma mark Trait_Class #pragma mark - -EidosClass *gSLiM_Trait_Class = nullptr; +Trait_Class *gSLiM_Trait_Class = nullptr; const std::vector *Trait_Class::Properties(void) const { diff --git a/core/trait.h b/core/trait.h index 08ecb85e..989c3541 100644 --- a/core/trait.h +++ b/core/trait.h @@ -37,7 +37,8 @@ class Species; #include "eidos_class_Dictionary.h" -extern EidosClass *gSLiM_Trait_Class; +class Trait_Class; +extern Trait_Class *gSLiM_Trait_Class; class Trait : public EidosDictionaryRetained diff --git a/eidos/eidos_class_DataFrame.cpp b/eidos/eidos_class_DataFrame.cpp index 1ecce144..616f943d 100644 --- a/eidos/eidos_class_DataFrame.cpp +++ b/eidos/eidos_class_DataFrame.cpp @@ -1262,7 +1262,7 @@ static EidosValue_SP Eidos_ExecuteFunction_readCSV(const std::vector *EidosDataFrame_Class::Properties(void) const { diff --git a/eidos/eidos_class_DataFrame.h b/eidos/eidos_class_DataFrame.h index 96831067..aa3d04fb 100644 --- a/eidos/eidos_class_DataFrame.h +++ b/eidos/eidos_class_DataFrame.h @@ -30,7 +30,8 @@ #include "eidos_value.h" -extern EidosClass *gEidosDataFrame_Class; +class EidosDataFrame_Class; +extern EidosDataFrame_Class *gEidosDataFrame_Class; class EidosDataFrame : public EidosDictionaryRetained diff --git a/eidos/eidos_class_Dictionary.cpp b/eidos/eidos_class_Dictionary.cpp index 419ed4bf..e5412d24 100644 --- a/eidos/eidos_class_Dictionary.cpp +++ b/eidos/eidos_class_Dictionary.cpp @@ -1621,7 +1621,7 @@ EidosValue_SP EidosDictionaryUnretained::ExecuteMethod_serialize(EidosGlobalStri #pragma mark EidosDictionaryUnretained_Class #pragma mark - -EidosClass *gEidosDictionaryUnretained_Class = nullptr; +EidosDictionaryUnretained_Class *gEidosDictionaryUnretained_Class = nullptr; const std::vector *EidosDictionaryUnretained_Class::Properties(void) const @@ -1922,7 +1922,7 @@ const EidosClass *EidosDictionaryRetained::Class(void) const #pragma mark EidosDictionaryRetained_Class #pragma mark - -EidosClass *gEidosDictionaryRetained_Class = nullptr; +EidosDictionaryRetained_Class *gEidosDictionaryRetained_Class = nullptr; const std::vector *EidosDictionaryRetained_Class::Functions(void) const diff --git a/eidos/eidos_class_Dictionary.h b/eidos/eidos_class_Dictionary.h index 3f6eb84a..d1495715 100644 --- a/eidos/eidos_class_Dictionary.h +++ b/eidos/eidos_class_Dictionary.h @@ -42,12 +42,17 @@ typedef std::unordered_map EidosDictionaryHashTable_Inte #endif +class EidosDictionaryUnretained_Class; +extern EidosDictionaryUnretained_Class *gEidosDictionaryUnretained_Class; + +class EidosDictionaryRetained_Class; +extern EidosDictionaryRetained_Class *gEidosDictionaryRetained_Class; + + #pragma mark - #pragma mark EidosDictionaryUnretained #pragma mark - -extern EidosClass *gEidosDictionaryUnretained_Class; - // These are helpers for EidosDictionaryUnretained. The purpose is to put all of its ivars into an allocated block, // so that the overhead of inheriting from the class itself is only one pointer, unless the Dictionary functionality is // actually used (which it usually isn't, since many SLiM objects inherit from Dictionary but rarely use it). @@ -252,8 +257,6 @@ class EidosDictionaryUnretained_Class : public EidosClass #pragma mark EidosDictionaryRetained #pragma mark - -extern EidosClass *gEidosDictionaryRetained_Class; - // A base class for EidosObject subclasses that are under retain/release. // There is a complication in Eidos here. When you make a new object with the Dictionary() class, you diff --git a/eidos/eidos_class_Image.cpp b/eidos/eidos_class_Image.cpp index 07c42021..51462d79 100644 --- a/eidos/eidos_class_Image.cpp +++ b/eidos/eidos_class_Image.cpp @@ -359,7 +359,7 @@ static EidosValue_SP Eidos_Instantiate_EidosImage(const std::vector *EidosImage_Class::Properties(void) const diff --git a/eidos/eidos_class_Image.h b/eidos/eidos_class_Image.h index 734dc4d1..0401e1a8 100644 --- a/eidos/eidos_class_Image.h +++ b/eidos/eidos_class_Image.h @@ -26,10 +26,12 @@ #ifndef __Eidos__eidos_class_image__ #define __Eidos__eidos_class_image__ + #include "eidos_value.h" -extern EidosClass *gEidosImage_Class; +class EidosImage_Class; +extern EidosImage_Class *gEidosImage_Class; class EidosImage : public EidosDictionaryRetained diff --git a/eidos/eidos_class_TestElement.cpp b/eidos/eidos_class_TestElement.cpp index 6df79bc0..c5c3876e 100644 --- a/eidos/eidos_class_TestElement.cpp +++ b/eidos/eidos_class_TestElement.cpp @@ -194,7 +194,7 @@ static EidosValue_SP Eidos_Instantiate_EidosTestElement(const std::vector *EidosTestElement_Class::Properties(void) const @@ -338,7 +338,7 @@ static EidosValue_SP Eidos_Instantiate_EidosTestElementNRR(const std::vector *EidosTestElementNRR_Class::Properties(void) const diff --git a/eidos/eidos_class_TestElement.h b/eidos/eidos_class_TestElement.h index ea3ad144..d3226611 100644 --- a/eidos/eidos_class_TestElement.h +++ b/eidos/eidos_class_TestElement.h @@ -27,9 +27,17 @@ #ifndef __Eidos__eidos_class_test_element__ #define __Eidos__eidos_class_test_element__ + #include "eidos_value.h" +class EidosTestElement_Class; +extern EidosTestElement_Class *gEidosTestElement_Class; + +class EidosTestElementNRR_Class; +extern EidosTestElementNRR_Class *gEidosTestElementNRR_Class; + + // // EidosTestElement is used for testing. It is a subclass of EidosDictionaryRetained, // and is under retain-release. It is instantiated with a hidden constructor: @@ -37,9 +45,6 @@ // (object<_TestElement>$)_Test(integer$ value) // -extern EidosClass *gEidosTestElement_Class; - - class EidosTestElement : public EidosDictionaryRetained { private: @@ -96,9 +101,6 @@ class EidosTestElement_Class : public EidosDictionaryRetained_Class // (object<_TestElementNRR>$)_TestNRR(integer$ value) // -extern EidosClass *gEidosTestElementNRR_Class; - - class EidosTestElementNRR : public EidosObject { private: From ea8067f3e61f88c6b613831d58d39e9afdba8f1e Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 31 Dec 2025 13:38:11 -0600 Subject: [PATCH 049/107] shift fitness calculations to being trait-based --- QtSLiM/help/SLiMHelpClasses.html | 2 +- SLiMgui/SLiMHelpClasses.rtf | 11 +- VERSIONS | 4 +- core/community.cpp | 4 +- core/haplosome.cpp | 10 +- core/individual.cpp | 75 +- core/individual.h | 17 +- core/interaction_type.cpp | 34 +- core/mutation.cpp | 46 +- core/mutation.h | 9 + core/mutation_run.cpp | 212 +-- core/mutation_run.h | 222 ++-- core/mutation_type.cpp | 12 +- core/polymorphism.cpp | 8 +- core/population.cpp | 88 +- core/slim_globals.h | 1 + core/slim_test_genetics.cpp | 2 - core/spatial_map.cpp | 30 +- core/species.cpp | 27 +- core/species.h | 2 +- core/species_eidos.cpp | 6 +- core/subpopulation.cpp | 1902 +++++---------------------- core/subpopulation.h | 55 +- core/substitution.cpp | 36 +- core/trait.cpp | 10 +- core/trait.h | 4 +- eidos/eidos_functions_colors.cpp | 8 +- eidos/eidos_globals.cpp | 8 +- eidos/eidos_globals.h | 7 + eidos/eidos_test_functions_math.cpp | 4 +- 30 files changed, 843 insertions(+), 2013 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index e188a97c..b942e4f1 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -1396,7 +1396,7 @@

baselineOffset <–> (float$)

The baseline offset for the trait.  This value is combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  A baseline offset value set through this property may therefore not be the value used by SLiM or subsequently returned by this property.

-

directFitnessEffect <–> (logical$)

+

directFitnessEffect => (logical$)

A logical flag indicating whether the trait has a direct fitness effect or not.  If T, the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component.  If F, the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a “fitness function”), or the trait might have other effects that are not obviously related to fitness at all.

index => (integer$)

The index of the trait in the vector of traits kept by the species.  The first trait defined in a species is at index 0, and subsequent traits count upwards from there.  The index of a trait is often used to refer to the trait, so it is important.  A global constant is defined for every trait, using each trait’s name, that provides the index of each trait, so this property will probably rarely be needed.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index cc96853a..535e8d9f 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -6057,8 +6057,7 @@ You can get the \f4\fs20 to access the dominance for that trait. The dominance coefficient(s) of a mutation can be changed with the \f3\fs18 setDominanceForTrait() \f4\fs20 method.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 If the target mutation has been configured to exhibit independent dominance by setting its dominance values to +If the target mutation has been configured to exhibit independent dominance by setting its dominance values to \f3\fs18 NAN \f4\fs20 , as discussed in \f3\fs18 initializeMutationType() @@ -6069,8 +6068,7 @@ You can get the \f4\fs20 ; instead, it provides the dominance values that will actually be used by SLiM to implement independent dominance, referred to as the \'93realized\'94 dominance values. These realized dominance values depend upon the mutation\'92s corresponding effects, and may change if those effects change. The class \f3\fs18 Trait \f4\fs20 documentation provides further discussion of independent dominance.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s dominance coefficient to some number \f3\fs18 x @@ -6317,8 +6315,7 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait \f4\fs20 .\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 If the target mutation has been configured to exhibit independent dominance by setting its dominance values to +If the target mutation has been configured to exhibit independent dominance by setting its dominance values to \f3\fs18 NAN \f4\fs20 , as discussed in \f3\fs18 initializeMutationType() @@ -14412,7 +14409,7 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f4\fs20 class. A baseline offset value set through this property may therefore not be the value used by SLiM or subsequently returned by this property.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 directFitnessEffect <\'96> (logical$)\ +\f3\fs18 \cf2 directFitnessEffect => (logical$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 A diff --git a/VERSIONS b/VERSIONS index fe5653ec..a9c33337 100644 --- a/VERSIONS +++ b/VERSIONS @@ -49,7 +49,7 @@ multitrait branch: add Species methods – (object)traitsWithIndices(integer indices) and – (object)traitsWithNames(string names) add Trait properties: baselineOffset <-> (float$) - directFitnessEffect <-> (logical$) + directFitnessEffect => (logical$) index => (integer$) individualOffsetMean <-> (float$) individualOffsetSD <-> (float$) @@ -121,6 +121,8 @@ multitrait branch: a new RealizedDominance() internal (C++) method calculates the correct dominance value to use when independent dominance is configured for a mutation Mutation and Substitution's dominanceForTrait() methods now use RealizedDominance(), and thus never report NAN as a dominance value (use isIndependentDominance to check for that) note that there is no concept of independent dominance for the hemizygous dominance coefficient, since hemizygous mutations are present in only one copy by definition + switch fitness calculation over to being based upon the calculated values of traits, rather than directly upon mutations + eliminate overhead for setting up fitness buffers in neutral WF models; this should provide a significant speedup for such models, if they don't use a mateChoice() callback version 5.1 (Eidos version 4.1): diff --git a/core/community.cpp b/core/community.cpp index 273dfbc9..a1843107 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -734,7 +734,7 @@ void Community::OptimizeScriptBlock(SLiMEidosBlock *p_script_block) expr_node = expr_node->children_[0]; // parse an optional constant at the beginning, like 1.0 + ... - double added_constant = NAN; + double added_constant = std::numeric_limits::quiet_NaN(); if ((expr_node->token_->token_type_ == EidosTokenType::kTokenPlus) && (expr_node->children_.size() == 2)) { @@ -755,7 +755,7 @@ void Community::OptimizeScriptBlock(SLiMEidosBlock *p_script_block) } // parse an optional divisor at the end, ... / div - double denominator = NAN; + double denominator = std::numeric_limits::quiet_NaN(); if ((expr_node->token_->token_type_ == EidosTokenType::kTokenDiv) && (expr_node->children_.size() == 2)) { diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 16bc8c96..640991eb 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -1337,7 +1337,7 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID if (mut_ptr->mutation_type_ptr_ == mutation_type_ptr) { MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mut_index); - effect_sum += mut_trait_info[0].effect_size_; + effect_sum += (double)mut_trait_info[0].effect_size_; } } } @@ -2974,7 +2974,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ // The selection coefficient was supplied by the user (i.e., not be from the mutation type's DES), so we set all_neutral_mutations_ also - if (selection_coeff != 0.0) + if (selection_coeff != (slim_effect_t)0.0) { species->pure_neutral_ = false; mutation_type_ptr->all_neutral_mutations_ = false; @@ -3476,7 +3476,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - if (selection_coeff != 0.0) + if (selection_coeff != (slim_effect_t)0.0) { species.pure_neutral_ = false; @@ -4134,7 +4134,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DES), so we set all_neutral_mutations_ also - if (selection_coeff != 0.0) + if (selection_coeff != (slim_effect_t)0.0) { species->pure_neutral_ = false; mutation_type_ptr->all_neutral_mutations_ = false; @@ -4412,7 +4412,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - if (mut_trait_info[trait_index].effect_size_ != 0.0) + if (mut_trait_info[trait_index].effect_size_ != (slim_effect_t)0.0) { any_nonneutral_removed = true; break; diff --git a/core/individual.cpp b/core/individual.cpp index 2468cf29..740aa061 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -55,7 +55,7 @@ bool Individual::s_any_individual_fitness_scaling_set_ = false; // individual first, haplosomes later; this is the new multichrom paradigm // BCH 10/12/2024: Note that this will rarely be called after simulation startup; see NewSubpopIndividual() // BCH 10/12/2025: Note also that NewSubpopIndividual() will rarely be called in WF models; see the Munge...() methods -Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age) : +Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, slim_fitness_t p_fitness, float p_mean_parent_age) : #ifdef SLIMGUI color_set_(false), #endif @@ -1411,7 +1411,7 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) if (mean_parent_age_ == -1) EIDOS_TERMINATION << "ERROR (Individual::GetProperty): property meanParentAge is not available in WF models." << EidosTerminate(); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mean_parent_age_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)mean_parent_age_)); } case gID_pedigreeID: // ACCELERATED { @@ -1720,7 +1720,7 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) } case gID_fitnessScaling: // ACCELERATED { - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(fitness_scaling_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)fitness_scaling_)); } case gEidosID_x: // ACCELERATED { @@ -1765,7 +1765,7 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) if (trait) { - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].phenotype_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[trait->Index()].phenotype_)); } return super::GetProperty(p_property_id); @@ -2018,7 +2018,7 @@ EidosValue *Individual::GetProperty_Accelerated_fitnessScaling(EidosGlobalString { Individual *value = (Individual *)(p_values[value_index]); - float_result->set_float_no_check(value->fitness_scaling_, value_index); + float_result->set_float_no_check((double)value->fitness_scaling_, value_index); } return float_result; @@ -2033,7 +2033,7 @@ EidosValue *Individual::GetProperty_Accelerated_x(EidosGlobalStringID p_property { Individual *value = (Individual *)(p_values[value_index]); - float_result->set_float_no_check(value->spatial_x_, value_index); + float_result->set_float_no_check((double)value->spatial_x_, value_index); } return float_result; @@ -2533,7 +2533,7 @@ EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID { const Individual *value = individuals_buffer[value_index]; - float_result->set_float_no_check(value->trait_info_[trait_index].phenotype_, value_index); + float_result->set_float_no_check((double)value->trait_info_[trait_index].phenotype_, value_index); } } else @@ -2545,7 +2545,7 @@ EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); int64_t trait_index = trait->Index(); - float_result->set_float_no_check(value->trait_info_[trait_index].phenotype_, value_index); + float_result->set_float_no_check((double)value->trait_info_[trait_index].phenotype_, value_index); } } @@ -2637,10 +2637,10 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue } case gID_fitnessScaling: // ACCELERATED { - fitness_scaling_ = p_value.FloatAtIndex_NOCAST(0, nullptr); + fitness_scaling_ = (slim_fitness_t)p_value.FloatAtIndex_NOCAST(0, nullptr); Individual::s_any_individual_fitness_scaling_set_ = true; - if ((fitness_scaling_ < 0.0) || (std::isnan(fitness_scaling_))) + if ((fitness_scaling_ < 0.0f) || (std::isnan(fitness_scaling_))) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property fitnessScaling must be >= 0.0." << EidosTerminate(); return; @@ -2901,7 +2901,7 @@ bool Individual::_SetFitnessScaling_1(double source_value, EidosObject **p_value EIDOS_THREAD_COUNT(gEidos_OMP_threads_SET_FITNESS_SCALE_1); #pragma omp parallel for simd schedule(simd:static) default(none) shared(p_values_size) firstprivate(p_values, source_value) if(parallel:p_values_size >= EIDOS_OMPMIN_SET_FITNESS_SCALE_1) num_threads(thread_count) for (size_t value_index = 0; value_index < p_values_size; ++value_index) - ((Individual *)(p_values[value_index]))->fitness_scaling_ = source_value; + ((Individual *)(p_values[value_index]))->fitness_scaling_ = (slim_fitness_t)source_value; return false; } @@ -2924,7 +2924,7 @@ bool Individual::_SetFitnessScaling_N(const double *source_data, EidosObject **p if ((source_value < 0.0) || (std::isnan(source_value))) saw_error = true; - ((Individual *)(p_values[value_index]))->fitness_scaling_ = source_value; + ((Individual *)(p_values[value_index]))->fitness_scaling_ = (slim_fitness_t)source_value; } return saw_error; @@ -3357,7 +3357,7 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met int64_t trait_index = trait_indices[0]; slim_effect_t offset = trait_info_[trait_index].offset_; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(offset)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)offset)); } else { @@ -3367,13 +3367,14 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met { slim_effect_t offset = trait_info_[trait_index].offset_; - float_result->push_float_no_check(offset); + float_result->push_float_no_check((double)offset); } return EidosValue_SP(float_result); } } +// FIXME MULTITRAIT: Individual needs a +setPhenotypeForTrait() method also, not least to invalidate phenotypes // ********************* - (float)phenotypeForTrait([Niso trait = NULL]) // EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -3391,7 +3392,7 @@ EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_ int64_t trait_index = trait_indices[0]; slim_effect_t phenotype = trait_info_[trait_index].phenotype_; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(phenotype)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)phenotype)); } else { @@ -3401,7 +3402,7 @@ EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_ { slim_effect_t phenotype = trait_info_[trait_index].phenotype_; - float_result->push_float_no_check(phenotype); + float_result->push_float_no_check((double)phenotype); } return EidosValue_SP(float_result); @@ -3573,7 +3574,7 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb if (mut_ptr->mutation_type_ptr_ == mutation_type_ptr) { MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mut_index); - effect_sum += mut_trait_info[0].effect_size_; + effect_sum += (double)mut_trait_info[0].effect_size_; } } } @@ -4350,7 +4351,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin slim_effect_t offset = trait->DrawIndividualOffset(); // effects for multiplicative traits are clamped to a minimum of 0.0 - if (offset < 0.0) + if (offset < (slim_effect_t)0.0) offset = 0.0; ind->trait_info_[trait_index].offset_ = offset; @@ -4378,7 +4379,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin slim_effect_t offset_for_trait = offset; // effects for multiplicative traits are clamped to a minimum of 0.0 - if ((trait->Type() == TraitType::kMultiplicative) && (offset < 0.0)) + if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) offset = 0.0; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) @@ -4396,7 +4397,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(offset_index++, nullptr)); // effects for multiplicative traits are clamped to a minimum of 0.0 - if ((trait->Type() == TraitType::kMultiplicative) && (offset < 0.0)) + if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) offset = 0.0; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) @@ -4429,7 +4430,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin slim_effect_t offset = static_cast(*(offsets_int++)); // effects for multiplicative traits are clamped to a minimum of 0.0 - if (offset < 0.0) + if (offset < (slim_effect_t)0.0) offset = 0.0; individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; @@ -4457,7 +4458,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin slim_effect_t offset = static_cast(*(offsets_int++)); // effects for multiplicative traits are clamped to a minimum of 0.0 - if ((trait->Type() == TraitType::kMultiplicative) && (offset < 0.0)) + if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) offset = 0.0; ind->trait_info_[trait_index].offset_ = offset; @@ -4483,7 +4484,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin slim_effect_t offset = static_cast(*(offsets_float++)); // effects for multiplicative traits are clamped to a minimum of 0.0 - if (offset < 0.0) + if (offset < (slim_effect_t)0.0) offset = 0.0; individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; @@ -4511,7 +4512,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin slim_effect_t offset = static_cast(*(offsets_float++)); // effects for multiplicative traits are clamped to a minimum of 0.0 - if ((trait->Type() == TraitType::kMultiplicative) && (offset < 0.0)) + if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) offset = 0.0; ind->trait_info_[trait_index].offset_ = offset; @@ -5384,7 +5385,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal } // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - if (selection_coeff != 0.0) + if (selection_coeff != (slim_effect_t)0.0) { species->pure_neutral_ = false; mutation_type_ptr->all_neutral_mutations_ = false; @@ -5990,12 +5991,12 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual // // and the template for _IncorporateEffects_Diploid() (which handles the non-hemizygous diploid case): // - // template + // template < const bool f_additiveTrait, const bool f_callbacks, const bool f_singlecallback> // if (has_active_callbacks) { - // if we have any active callbacks, we have to account for all callbacks (active or not), since - // one callback might activate/deactivate another; inactive callbacks might not stay inactive + // If we have any active callbacks, we have to account for all callbacks (active or not), since + // one callback might activate/deactivate another; inactive callbacks might not stay inactive. // This callback applies to this subpopulation. We now need to determine which traits, if any, it applies to. // For each trait we keep a separate vector of callbacks that apply to that trait. for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) @@ -6121,7 +6122,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual // FIXME MULTITRAIT: We need to recache non-neutral caches for all mutation runs here when running parallel. // This is maybe also where we would recache the total phenotypic effect of any mutation runs for traits with // "independent dominance". -#warning recache non-neutral caches first +#warning re-enable non-neutral caches and recache non-neutral caches first // For a given individual, for a given trait, we have to make a decision as to whether we will recalculate or not. That decision gets made // once and then holds across all chromosomes for the individual. But we're looping over chromosomes at the topmost level, so we have a @@ -6458,7 +6459,7 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos { effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); - if (effect <= 0.0) { // not clamped to zero, so we check here + if (effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6567,7 +6568,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6602,7 +6603,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6651,7 +6652,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (homozygous_effect <= 0.0) { // not clamped to zero, so we check here + if (homozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6683,7 +6684,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6738,7 +6739,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6790,7 +6791,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } @@ -6820,7 +6821,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - if (heterozygous_effect <= 0.0) { // not clamped to zero, so we check here + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; return; } diff --git a/core/individual.h b/core/individual.h index 00743d60..385d1b70 100644 --- a/core/individual.h +++ b/core/individual.h @@ -143,14 +143,15 @@ class Individual : public EidosDictionaryUnretained slim_usertag_t tag_value_; // a user-defined tag value of integer type double tagF_value_; // a user-defined tag value of float type - double fitness_scaling_ = 1.0; // the fitnessScaling property value - double cached_fitness_UNSAFE_; // the last calculated fitness value for this individual; NaN for new offspring, 1.0 for new subpops - // this is marked UNSAFE because Subpopulation's individual_cached_fitness_OVERRIDE_ flag can override - // this value in neutral models; that flag must be checked before using this cached value + // FIXME MULTITRAIT: We should also have a typedef for trait indices, and it should be int32_t, again for speed/size; get rid of int64_t for this + slim_fitness_t fitness_scaling_ = 1.0f; // the fitnessScaling property value + slim_fitness_t cached_fitness_UNSAFE_; // the last calculated fitness value for this individual; NaN for new offspring, 1.0 for new subpops + // this is marked UNSAFE because Subpopulation's individual_cached_fitness_OVERRIDE_ flag can override + // this value in constant-fitness models; that flag must be checked before using this cached value #ifdef SLIMGUI - double cached_unscaled_fitness_; // the last calculated fitness value for this individual, WITHOUT subpop fitnessScaling; used only in - // in SLiMgui, which wants to exclude that scaling because it usually represents density-dependence - // that confuses interpretation; note that individual_cached_fitness_OVERRIDE_ is not relevant to this + slim_fitness_t cached_unscaled_fitness_; // the last calculated fitness value for this individual, WITHOUT subpop fitnessScaling; used only in + // in SLiMgui, which wants to exclude that scaling because it usually represents density-dependence + // that confuses interpretation; note that individual_cached_fitness_OVERRIDE_ is not relevant to this #endif Haplosome *hapbuffer_[2]; // *(hapbuffer_[2]), an internal buffer used to avoid allocation and increase memory locality @@ -177,7 +178,7 @@ class Individual : public EidosDictionaryUnretained Individual(const Individual &p_original) = delete; Individual& operator= (const Individual &p_original) = delete; // no copy construction Individual(void) = delete; // no null construction - Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age); + Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, slim_fitness_t p_fitness, float p_mean_parent_age); virtual ~Individual(void) override; void _InitializePerTraitInformation(void); diff --git a/core/interaction_type.cpp b/core/interaction_type.cpp index 950568fa..b4453da3 100755 --- a/core/interaction_type.cpp +++ b/core/interaction_type.cpp @@ -2952,7 +2952,7 @@ void InteractionType::FillSparseVectorForReceiverStrengths(SparseVector *sv, Ind { sv_value_t distance = values[col_iter]; - values[col_iter] = (sv_value_t)CalculateStrengthNoCallbacks(distance); + values[col_iter] = (sv_value_t)CalculateStrengthNoCallbacks((double)distance); } EIDOS_TERMINATION << "ERROR (InteractionType::FillSparseVectorForReceiverStrengths): (internal error) unimplemented SpatialKernelType case." << EidosTerminate(); @@ -2971,7 +2971,7 @@ void InteractionType::FillSparseVectorForReceiverStrengths(SparseVector *sv, Ind uint32_t col = columns[col_iter]; sv_value_t distance = values[col_iter]; - values[col_iter] = (sv_value_t)CalculateStrengthWithCallbacks(distance, receiver, subpop_individuals[col], interaction_callbacks); + values[col_iter] = (sv_value_t)CalculateStrengthWithCallbacks((double)distance, receiver, subpop_individuals[col], interaction_callbacks); } } @@ -4306,7 +4306,7 @@ EidosValue_SP InteractionType::ExecuteMethod_drawByStrength(EidosGlobalStringID double strength = 0; if ((exerter_index_in_subpop != receiver_index) && CheckIndividualConstraints(exerter, exerter_constraints_)) // potentially raises - strength = ApplyInteractionCallbacks(receiver, exerter, if_param1_, NAN, callbacks); // hard-coding interaction function "f" (SpatialKernelType::kFixed), which is required + strength = ApplyInteractionCallbacks(receiver, exerter, if_param1_, std::numeric_limits::quiet_NaN(), callbacks); // hard-coding interaction function "f" (SpatialKernelType::kFixed), which is required total_interaction_strength += strength; cached_strength.emplace_back(strength); @@ -4402,7 +4402,7 @@ EidosValue_SP InteractionType::ExecuteMethod_drawByStrength(EidosGlobalStringID { sv_value_t strength = strengths[col_index]; - total_interaction_strength += strength; + total_interaction_strength += (double)strength; double_strengths.emplace_back((double)strength); } @@ -4570,7 +4570,7 @@ EidosValue_SP InteractionType::ExecuteMethod_drawByStrength(EidosGlobalStringID { sv_value_t strength = strengths[col_index]; - total_interaction_strength += strength; + total_interaction_strength += (double)strength; double_strengths.emplace_back((double)strength); } @@ -4920,7 +4920,7 @@ EidosValue_SP InteractionType::ExecuteMethod_localPopulationDensity(EidosGlobalS total_strength = 0.0; for (uint32_t col_index = 0; col_index < nnz; ++col_index) - total_strength += strengths[col_index]; + total_strength += (double)strengths[col_index]; } catch (...) { InteractionType::FreeSparseVector(sv); throw; @@ -5016,7 +5016,7 @@ EidosValue_SP InteractionType::ExecuteMethod_localPopulationDensity(EidosGlobalS total_strength = 0.0; for (uint32_t col_index = 0; col_index < nnz; ++col_index) - total_strength += strengths[col_index]; + total_strength += (double)strengths[col_index]; } catch (...) { InteractionType::FreeSparseVector(sv); saw_error_4 = true; @@ -5122,10 +5122,10 @@ EidosValue_SP InteractionType::ExecuteMethod_interactionDistance(EidosGlobalStri double *result_ptr = result_vec->data_mutable(); for (int exerter_index = 0; exerter_index < exerter_subpop_size; ++exerter_index) - *(result_ptr + exerter_index) = INFINITY; + *(result_ptr + exerter_index) = std::numeric_limits::infinity(); for (uint32_t col_index = 0; col_index < nnz; ++col_index) - *(result_ptr + columns[col_index]) = distances[col_index]; + *(result_ptr + columns[col_index]) = (double)distances[col_index]; InteractionType::FreeSparseVector(sv); return result_SP; @@ -5163,7 +5163,7 @@ EidosValue_SP InteractionType::ExecuteMethod_interactionDistance(EidosGlobalStri if ((exerter == receiver) || !CheckIndividualConstraints(exerter, exerter_constraints_)) { // self-interactions and constraints result in an interaction distance of INF - result_vec->set_float_no_check(INFINITY, exerter_index); + result_vec->set_float_no_check(std::numeric_limits::infinity(), exerter_index); } else { @@ -5177,7 +5177,7 @@ EidosValue_SP InteractionType::ExecuteMethod_interactionDistance(EidosGlobalStri if (distance > max_distance_) { // interactions beyond the maximum interaction distance also produce INF - result_vec->set_float_no_check(INFINITY, exerter_index); + result_vec->set_float_no_check(std::numeric_limits::infinity(), exerter_index); } else { @@ -5196,7 +5196,7 @@ EidosValue_SP InteractionType::ExecuteMethod_interactionDistance(EidosGlobalStri double *result_ptr = result_vec->data_mutable(); for (int exerter_index = 0; exerter_index < exerter_subpop_size; ++exerter_index) - *(result_ptr + exerter_index) = INFINITY; + *(result_ptr + exerter_index) = std::numeric_limits::infinity(); return result_SP; } @@ -6097,7 +6097,7 @@ EidosValue_SP InteractionType::ExecuteMethod_strength(EidosGlobalStringID p_meth EIDOS_BZERO(result_ptr, exerter_subpop_size * sizeof(double)); for (uint32_t col_index = 0; col_index < nnz; ++col_index) - *(result_ptr + columns[col_index]) = strengths[col_index]; + *(result_ptr + columns[col_index]) = (double)strengths[col_index]; InteractionType::FreeSparseVector(sv); return result_SP; @@ -6190,7 +6190,7 @@ EidosValue_SP InteractionType::ExecuteMethod_strength(EidosGlobalStringID p_meth Individual *exerter = exerter_subpop->parent_individuals_[exerter_index]; if (CheckIndividualConstraints(exerter, exerter_constraints_)) // potentially raises - strength = ApplyInteractionCallbacks(receiver, exerter, if_param1_, NAN, callbacks); // hard-coding interaction function "f" (SpatialKernelType::kFixed), which is required + strength = ApplyInteractionCallbacks(receiver, exerter, if_param1_, std::numeric_limits::quiet_NaN(), callbacks); // hard-coding interaction function "f" (SpatialKernelType::kFixed), which is required } result_vec->set_float_no_check(strength, exerter_index); @@ -6219,7 +6219,7 @@ EidosValue_SP InteractionType::ExecuteMethod_strength(EidosGlobalStringID p_meth double strength = 0; if ((exerter_index_in_subpop != receiver_index) && CheckIndividualConstraints(exerter, exerter_constraints_)) // potentially raises - strength = ApplyInteractionCallbacks(receiver, exerter, if_param1_, NAN, callbacks); // hard-coding interaction function "f" (SpatialKernelType::kFixed), which is required + strength = ApplyInteractionCallbacks(receiver, exerter, if_param1_, std::numeric_limits::quiet_NaN(), callbacks); // hard-coding interaction function "f" (SpatialKernelType::kFixed), which is required result_vec->set_float_no_check(strength, exerter_index); } @@ -6399,7 +6399,7 @@ EidosValue_SP InteractionType::ExecuteMethod_totalOfNeighborStrengths(EidosGloba double total_strength = 0.0; for (uint32_t col_index = 0; col_index < nnz; ++col_index) - total_strength += strengths[col_index]; + total_strength += (double)strengths[col_index]; InteractionType::FreeSparseVector(sv); return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(total_strength)); @@ -6469,7 +6469,7 @@ EidosValue_SP InteractionType::ExecuteMethod_totalOfNeighborStrengths(EidosGloba double total_strength = 0.0; for (uint32_t col_index = 0; col_index < nnz; ++col_index) - total_strength += strengths[col_index]; + total_strength += (double)strengths[col_index]; result_vec->set_float_no_check(total_strength, receiver_index); InteractionType::FreeSparseVector(sv); diff --git a/core/mutation.cpp b/core/mutation.cpp index b92811bd..9417393d 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -86,7 +86,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; - if (effect != 0.0) + if (effect != (slim_effect_t)0.0) { is_neutral_ = false; @@ -217,7 +217,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; - if (effect != 0.0) + if (effect != (slim_effect_t)0.0) { is_neutral_ = false; @@ -313,7 +313,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; - if (effect != 0.0) + if (effect != (slim_effect_t)0.0) { is_neutral_ = false; @@ -428,7 +428,7 @@ void Mutation::SelfConsistencyCheck(const std::string &p_message_end) if (correct_hemizygous_effect != traitInfoRec.hemizygous_effect_) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) hemizygous_effect_ does not match expectations" << p_message_end << "." << EidosTerminate(); - if (traitInfoRec.effect_size_ != 0.0) + if (traitInfoRec.effect_size_ != (slim_effect_t)0.0) all_neutral_effects = false; } @@ -463,7 +463,7 @@ slim_effect_t Mutation::RealizedDominanceForTrait(Trait *p_trait) if (effect_size == (slim_effect_t)0.0) return (slim_effect_t)0.5; - if (effect_size <= -1.0) + if (effect_size <= (slim_effect_t)-1.0) return (slim_effect_t)1.0; // do the math in double-precision float to avoid numerical error @@ -484,9 +484,9 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e traitInfoRec->effect_size_ = p_new_effect; - if (p_new_effect != 0.0) + if (p_new_effect != (slim_effect_t)0.0) { - if (old_effect == 0.0) + if (old_effect == (slim_effect_t)0.0) { // This mutation is no longer neutral; various observers care about that change is_neutral_ = false; @@ -534,7 +534,7 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - if ((mut_trait_info + trait_index)->effect_size_ != 0.0) + if ((mut_trait_info + trait_index)->effect_size_ != (slim_effect_t)0.0) { is_neutral_ = false; break; @@ -694,7 +694,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[0].effect_size_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)mut_trait_info[0].effect_size_)); else if (trait_count == 0) return gStaticEidosValue_Float_ZeroVec; else @@ -705,7 +705,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) { slim_effect_t effect = mut_trait_info[trait_index].effect_size_; - float_result->push_float_no_check(effect); + float_result->push_float_no_check((double)effect); } return EidosValue_SP(float_result); @@ -724,7 +724,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) { slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[0]); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)realized_dominance)); } else if (trait_count == 0) { @@ -738,7 +738,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) { slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[trait_index]); - float_result->push_float_no_check(realized_dominance); + float_result->push_float_no_check((double)realized_dominance); } return EidosValue_SP(float_result); @@ -755,7 +755,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[0].hemizygous_dominance_coeff_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)mut_trait_info[0].hemizygous_dominance_coeff_)); else if (trait_count == 0) return gStaticEidosValue_Float_ZeroVec; else @@ -766,7 +766,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) { slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; - float_result->push_float_no_check(dominance); + float_result->push_float_no_check((double)dominance); } return EidosValue_SP(float_result); @@ -835,7 +835,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].effect_size_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)mut_trait_info[trait->Index()].effect_size_)); } } else if ((property_string.length() > 19) && Eidos_string_hasSuffix(property_string, "HemizygousDominance")) @@ -848,7 +848,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(mut_trait_info[trait->Index()].hemizygous_dominance_coeff_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)mut_trait_info[trait->Index()].hemizygous_dominance_coeff_)); } } else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) @@ -861,7 +861,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // Note that we use RealizedDominanceForTrait() here so that an independent dominance of NAN gets handled. slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)realized_dominance)); } } @@ -1255,7 +1255,7 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho int64_t trait_index = trait_indices[0]; slim_effect_t effect = mut_trait_info[trait_index].effect_size_; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(effect)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)effect)); } else { @@ -1265,7 +1265,7 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho { slim_effect_t effect = mut_trait_info[trait_index].effect_size_; - float_result->push_float_no_check(effect); + float_result->push_float_no_check((double)effect); } return EidosValue_SP(float_result); @@ -1292,7 +1292,7 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me Trait *trait = traits[trait_index]; slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)realized_dominance)); } else { @@ -1303,7 +1303,7 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me Trait *trait = traits[trait_index]; slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - float_result->push_float_no_check(realized_dominance); + float_result->push_float_no_check((double)realized_dominance); } return EidosValue_SP(float_result); @@ -1331,7 +1331,7 @@ EidosValue_SP Mutation::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStr int64_t trait_index = trait_indices[0]; slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)dominance)); } else { @@ -1341,7 +1341,7 @@ EidosValue_SP Mutation::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStr { slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; - float_result->push_float_no_check(dominance); + float_result->push_float_no_check((double)dominance); } return EidosValue_SP(float_result); diff --git a/core/mutation.h b/core/mutation.h index b7935e91..4b0fb44b 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -105,6 +105,15 @@ class Mutation : public EidosDictionaryRetained // The state of is_neutral_ is updated to reflect the current state of the mutation whenever it changes. // This is used to make constructing non-neutral caches for trait evaluation fast with multiple traits. unsigned int is_neutral_ : 1; + // FIXME MULTITRAIT: it occurs to me that the present is_neutral_ flag on mutations is ambiguous. One meaning + // is "this mutation has neutral effects for all traits"; such mutations can be disregarded in all phenotype + // calculations. The other is "this mutation has neutral effects for all traits *that have a direct fitness + // effect*"; such mutations can be disregarded for all calculations leading to a fitness value. The former + // is the meaning that needs to determine whether a given mutation is placed into a non-neutral cache, since + // the non-neutral caches will be used for all phenotype calculations (I THINK?). The latter is the meaning + // that should be used to determine whether a given trait with a direct fitness effect is considered to be + // neutral or not; if any mutation has a non-neutral effect on that given trait, then that trait needs to be + // demanded and factored in to fitness calculations. // is_independent_dominance_ is true if the mutation has been configured to exhibit "independent dominance", // meaning that two heterozygous effects equal one homozygous effect, allowing the effects from haplosomes diff --git a/core/mutation_run.cpp b/core/mutation_run.cpp index 1fc437fc..8fdfbeed 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -444,112 +444,112 @@ void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_hal #if SLIM_USE_NONNEUTRAL_CACHES -void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const -{ - // - // Regime 1 means there are no mutationEffect() callbacks at all, so neutrality can be assessed - // simply by looking at selection_coeff_ != 0.0. The mutation type is irrelevant. - // - zero_out_nonneutral_buffer(); - - // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed - for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) - { - MutationIndex mutindex = mutations_[bufindex]; - Mutation *mutptr = p_mut_block_ptr + mutindex; - - if (!mutptr->is_neutral_) - add_to_nonneutral_buffer(mutindex); - } -} - -void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const -{ - // FIXME MULTICHROM: I think regime 2 needs to be rethought with multitrait. We won't have - // constant mutationEffect() callbacks any more; all of those optimizations, including regime 2, - // can be ripped out. Instead, QTL mutations will contribute an additive effect to a quantitative - // trait, and their effect on whatever multiplicative trait might be in the model will be zero - // (absent pleiotropy). That is the case that we will now want to detect and optimize somehow. - // I'm not sure what the right strategy would be. What exactly will the role of non-neutral - // caches be? Should mutations that are non-neutral for *any* trait be put into them, which would - // be best for universal pleiotropy? Or maybe we have separate non-neutral caches for each trait, - // which would be best for zero pleiotropy? Or some kind of adaptive approach? - - // - // Regime 2 means the only mutationEffect() callbacks are (a) constant-effect, (b) neutral (i.e., - // make their mutation type become neutral), and (c) global (i.e. apply to all subpopulations). - // Here neutrality is assessed by first consulting the set_neutral_by_global_active_callback - // flag of MutationType, which is set up by RecalculateFitness() for us. If that is true, - // the mutation is neutral; if false, selection_coeff_ is reliable. Note the code below uses - // the exact way that the C operator && works to implement this order of checks. - // - zero_out_nonneutral_buffer(); - - // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed - for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) - { - MutationIndex mutindex = mutations_[bufindex]; - Mutation *mutptr = p_mut_block_ptr + mutindex; - - // The result of && is not order-dependent, but the first condition is checked first. - // I expect many mutations would fail the first test (thus short-circuiting), whereas - // few would fail the second test (i.e. actually be 0.0) in a QTL model. - if ((!mutptr->mutation_type_ptr_->set_neutral_by_global_active_callback_) && !mutptr->is_neutral_) - add_to_nonneutral_buffer(mutindex); - } -} - -void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const -{ - // - // Regime 3 means that there are mutationEffect() callbacks beyond the constant neutral global - // callbacks of regime 2, so if a mutation's muttype is subject to any mutationEffect() callbacks - // at all, whether active or not, that mutation must be considered to be non-neutral (because - // a rogue callback could enable/disable other callbacks). This is determined by consulting - // the subject_to_mutationEffect_callback flag of MutationType, set up by RecalculateFitness() - // for us. If that flag is not set, then the selection_coeff_ is reliable as usual. - // - zero_out_nonneutral_buffer(); - - // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed - for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) - { - MutationIndex mutindex = mutations_[bufindex]; - Mutation *mutptr = p_mut_block_ptr + mutindex; - - // The result of || is not order-dependent, but the first condition is checked first. - // I have reordered this to put the fast test first; or I'm guessing it's the fast test. - if (!mutptr->is_neutral_ || (mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_)) - add_to_nonneutral_buffer(mutindex); - } -} - -void MutationRun::check_nonneutral_mutation_cache() const -{ - if (!nonneutral_mutations_) - EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache not allocated." << EidosTerminate(); - if (nonneutral_mutations_count_ == -1) - EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) unvalidated cache." << EidosTerminate(); - if (nonneutral_mutations_count_ > nonneutral_mutation_capacity_) - EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache size exceeds cache capacity." << EidosTerminate(); - - // Check for correctness in regime 1. Now that we have three regimes, this isn't really worth maintaining; - // it really just replicates the above logic exactly, so it is not a very effective cross-check. - - /* - int32_t cache_index = 0; - - for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) - { - MutationIndex mutindex = mutations_[bufindex]; - Mutation *mutptr = gSLiM_Mutation_Block + mutindex; - - if (mutptr->selection_coeff_ != 0.0) - if (*(nonneutral_mutations_ + cache_index++) != mutindex) - EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache_REGIME_1): (internal error) unsynchronized cache." << EidosTerminate(); - } - */ -} +//void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const +//{ +// // +// // Regime 1 means there are no mutationEffect() callbacks at all, so neutrality can be assessed +// // simply by looking at selection_coeff_ != 0.0. The mutation type is irrelevant. +// // +// zero_out_nonneutral_buffer(); +// +// // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed +// for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) +// { +// MutationIndex mutindex = mutations_[bufindex]; +// Mutation *mutptr = p_mut_block_ptr + mutindex; +// +// if (!mutptr->is_neutral_) +// add_to_nonneutral_buffer(mutindex); +// } +//} +// +//void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const +//{ +// // FIXME MULTICHROM: I think regime 2 needs to be rethought with multitrait. We won't have +// // constant mutationEffect() callbacks any more; all of those optimizations, including regime 2, +// // can be ripped out. Instead, QTL mutations will contribute an additive effect to a quantitative +// // trait, and their effect on whatever multiplicative trait might be in the model will be zero +// // (absent pleiotropy). That is the case that we will now want to detect and optimize somehow. +// // I'm not sure what the right strategy would be. What exactly will the role of non-neutral +// // caches be? Should mutations that are non-neutral for *any* trait be put into them, which would +// // be best for universal pleiotropy? Or maybe we have separate non-neutral caches for each trait, +// // which would be best for zero pleiotropy? Or some kind of adaptive approach? +// +// // +// // Regime 2 means the only mutationEffect() callbacks are (a) constant-effect, (b) neutral (i.e., +// // make their mutation type become neutral), and (c) global (i.e. apply to all subpopulations). +// // Here neutrality is assessed by first consulting the set_neutral_by_global_active_callback +// // flag of MutationType, which is set up by RecalculateFitness() for us. If that is true, +// // the mutation is neutral; if false, selection_coeff_ is reliable. Note the code below uses +// // the exact way that the C operator && works to implement this order of checks. +// // +// zero_out_nonneutral_buffer(); +// +// // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed +// for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) +// { +// MutationIndex mutindex = mutations_[bufindex]; +// Mutation *mutptr = p_mut_block_ptr + mutindex; +// +// // The result of && is not order-dependent, but the first condition is checked first. +// // I expect many mutations would fail the first test (thus short-circuiting), whereas +// // few would fail the second test (i.e. actually be 0.0) in a QTL model. +// if ((!mutptr->mutation_type_ptr_->set_neutral_by_global_active_callback_) && !mutptr->is_neutral_) +// add_to_nonneutral_buffer(mutindex); +// } +//} +// +//void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const +//{ +// // +// // Regime 3 means that there are mutationEffect() callbacks beyond the constant neutral global +// // callbacks of regime 2, so if a mutation's muttype is subject to any mutationEffect() callbacks +// // at all, whether active or not, that mutation must be considered to be non-neutral (because +// // a rogue callback could enable/disable other callbacks). This is determined by consulting +// // the subject_to_mutationEffect_callback flag of MutationType, set up by RecalculateFitness() +// // for us. If that flag is not set, then the selection_coeff_ is reliable as usual. +// // +// zero_out_nonneutral_buffer(); +// +// // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed +// for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) +// { +// MutationIndex mutindex = mutations_[bufindex]; +// Mutation *mutptr = p_mut_block_ptr + mutindex; +// +// // The result of || is not order-dependent, but the first condition is checked first. +// // I have reordered this to put the fast test first; or I'm guessing it's the fast test. +// if (!mutptr->is_neutral_ || (mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_)) +// add_to_nonneutral_buffer(mutindex); +// } +//} +// +//void MutationRun::check_nonneutral_mutation_cache() const +//{ +// if (!nonneutral_mutations_) +// EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache not allocated." << EidosTerminate(); +// if (nonneutral_mutations_count_ == -1) +// EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) unvalidated cache." << EidosTerminate(); +// if (nonneutral_mutations_count_ > nonneutral_mutation_capacity_) +// EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache size exceeds cache capacity." << EidosTerminate(); +// +// // Check for correctness in regime 1. Now that we have three regimes, this isn't really worth maintaining; +// // it really just replicates the above logic exactly, so it is not a very effective cross-check. +// +// /* +// int32_t cache_index = 0; +// +// for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) +// { +// MutationIndex mutindex = mutations_[bufindex]; +// Mutation *mutptr = gSLiM_Mutation_Block + mutindex; +// +// if (mutptr->selection_coeff_ != 0.0) +// if (*(nonneutral_mutations_ + cache_index++) != mutindex) +// EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache_REGIME_1): (internal error) unsynchronized cache." << EidosTerminate(); +// } +// */ +//} #endif diff --git a/core/mutation_run.h b/core/mutation_run.h index f0e7ec9f..bcca5816 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -699,117 +699,117 @@ class MutationRun #if SLIM_USE_NONNEUTRAL_CACHES // caching non-neutral mutations; see above for comments about the "regime" etc. - inline __attribute__((always_inline)) void zero_out_nonneutral_buffer(void) const - { - if (!nonneutral_mutations_) - { - // If we don't have a buffer allocated yet, follow the same rules as for the main mutation buffer - nonneutral_mutation_capacity_ = SLIM_MUTRUN_INITIAL_CAPACITY; - nonneutral_mutations_ = (MutationIndex *)malloc(nonneutral_mutation_capacity_ * sizeof(MutationIndex)); - if (!nonneutral_mutations_) - EIDOS_TERMINATION << "ERROR (MutationRun::zero_out_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - } - - // empty out the current buffer contents - nonneutral_mutations_count_ = 0; - } - - inline __attribute__((always_inline)) void add_to_nonneutral_buffer(MutationIndex p_mutation_index) const - { - // This is basically the emplace_back() code, but for the nonneutral buffer - if (nonneutral_mutations_count_ == nonneutral_mutation_capacity_) - { -#ifdef __clang_analyzer__ - assert(nonneutral_mutation_capacity_ > 0); -#endif - - if (nonneutral_mutation_capacity_ < 32) - nonneutral_mutation_capacity_ <<= 1; // double the number of pointers we can hold - else - nonneutral_mutation_capacity_ += 16; - - nonneutral_mutations_ = (MutationIndex *)realloc(nonneutral_mutations_, nonneutral_mutation_capacity_ * sizeof(MutationIndex)); - if (!nonneutral_mutations_) - EIDOS_TERMINATION << "ERROR (MutationRun::add_to_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - } - - *(nonneutral_mutations_ + nonneutral_mutations_count_) = p_mutation_index; - ++nonneutral_mutations_count_; - } - - void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const; - void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const; - void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const; - - void check_nonneutral_mutation_cache() const; - - inline __attribute__((always_inline)) void beginend_nonneutral_pointers(Mutation *p_mut_block_ptr, const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const - { - if ((nonneutral_change_validation_ != p_nonneutral_change_counter) || (nonneutral_mutations_count_ == -1)) - { - // When running parallel, all nonneutral caches must be validated - // ahead of time; see Subpopulation::FixNonNeutralCaches_OMP() - THREAD_SAFETY_IN_ACTIVE_PARALLEL("beginend_nonneutral_pointers()"); - - // If the nonneutral change counter has changed since we last validated, or our cache is invalid for other - // reasons (most notably being a new mutation run that has not yet cached), validate it immediately - nonneutral_change_validation_ = p_nonneutral_change_counter; - - switch (p_nonneutral_regime) - { - case 1: cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr); break; - case 2: cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr); break; - case 3: cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr); break; - } - -#if (SLIMPROFILING == 1) - // PROFILING - recached_run_ = true; -#endif - } - -#if DEBUG - check_nonneutral_mutation_cache(); -#endif - - // Return the requested pointers to allow the caller to iterate over the nonneutral mutation buffer - *p_mutptr_iter = nonneutral_mutations_; - *p_mutptr_max = nonneutral_mutations_ + nonneutral_mutations_count_; - } - -#ifdef _OPENMP - // This is used by Subpopulation::FixNonNeutralCaches_OMP() to validate - // these caches; it starts a new task if the nonneutral cache is invalid - // This method is called from within a "single" construct. - inline __attribute__((always_inline)) void validate_nonneutral_cache(int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const - { - if ((nonneutral_change_validation_ != p_nonneutral_change_counter) || (nonneutral_mutations_count_ == -1)) - { - // If the nonneutral change counter has changed since we last validated, or our cache is invalid for other - // reasons (most notably being a new mutation run that has not yet cached), validate it with an OpenMP task - // We set up these variables to prevent ourselves from seeing the cache as invalid again - nonneutral_change_validation_ = p_nonneutral_change_counter; - nonneutral_mutations_count_ = 0; - -#if (SLIMPROFILING == 1) - // PROFILING - recached_run_ = true; -#endif - - // I tried splitting the below code out into its own non-inline method, - // but that seemed to trigger a compiler bug, so here we are. -#pragma omp task - { - switch (p_nonneutral_regime) - { - case 1: cache_nonneutral_mutations_REGIME_1(); break; - case 2: cache_nonneutral_mutations_REGIME_2(); break; - case 3: cache_nonneutral_mutations_REGIME_3(); break; - } - } - } - } -#endif +// inline __attribute__((always_inline)) void zero_out_nonneutral_buffer(void) const +// { +// if (!nonneutral_mutations_) +// { +// // If we don't have a buffer allocated yet, follow the same rules as for the main mutation buffer +// nonneutral_mutation_capacity_ = SLIM_MUTRUN_INITIAL_CAPACITY; +// nonneutral_mutations_ = (MutationIndex *)malloc(nonneutral_mutation_capacity_ * sizeof(MutationIndex)); +// if (!nonneutral_mutations_) +// EIDOS_TERMINATION << "ERROR (MutationRun::zero_out_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); +// } +// +// // empty out the current buffer contents +// nonneutral_mutations_count_ = 0; +// } +// +// inline __attribute__((always_inline)) void add_to_nonneutral_buffer(MutationIndex p_mutation_index) const +// { +// // This is basically the emplace_back() code, but for the nonneutral buffer +// if (nonneutral_mutations_count_ == nonneutral_mutation_capacity_) +// { +//#ifdef __clang_analyzer__ +// assert(nonneutral_mutation_capacity_ > 0); +//#endif +// +// if (nonneutral_mutation_capacity_ < 32) +// nonneutral_mutation_capacity_ <<= 1; // double the number of pointers we can hold +// else +// nonneutral_mutation_capacity_ += 16; +// +// nonneutral_mutations_ = (MutationIndex *)realloc(nonneutral_mutations_, nonneutral_mutation_capacity_ * sizeof(MutationIndex)); +// if (!nonneutral_mutations_) +// EIDOS_TERMINATION << "ERROR (MutationRun::add_to_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); +// } +// +// *(nonneutral_mutations_ + nonneutral_mutations_count_) = p_mutation_index; +// ++nonneutral_mutations_count_; +// } +// +// void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const; +// void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const; +// void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const; +// +// void check_nonneutral_mutation_cache() const; +// +// inline __attribute__((always_inline)) void beginend_nonneutral_pointers(Mutation *p_mut_block_ptr, const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const +// { +// if ((nonneutral_change_validation_ != p_nonneutral_change_counter) || (nonneutral_mutations_count_ == -1)) +// { +// // When running parallel, all nonneutral caches must be validated +// // ahead of time; see Subpopulation::FixNonNeutralCaches_OMP() +// THREAD_SAFETY_IN_ACTIVE_PARALLEL("beginend_nonneutral_pointers()"); +// +// // If the nonneutral change counter has changed since we last validated, or our cache is invalid for other +// // reasons (most notably being a new mutation run that has not yet cached), validate it immediately +// nonneutral_change_validation_ = p_nonneutral_change_counter; +// +// switch (p_nonneutral_regime) +// { +// case 1: cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr); break; +// case 2: cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr); break; +// case 3: cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr); break; +// } +// +//#if (SLIMPROFILING == 1) +// // PROFILING +// recached_run_ = true; +//#endif +// } +// +//#if DEBUG +// check_nonneutral_mutation_cache(); +//#endif +// +// // Return the requested pointers to allow the caller to iterate over the nonneutral mutation buffer +// *p_mutptr_iter = nonneutral_mutations_; +// *p_mutptr_max = nonneutral_mutations_ + nonneutral_mutations_count_; +// } +// +//#ifdef _OPENMP +// // This is used by Subpopulation::FixNonNeutralCaches_OMP() to validate +// // these caches; it starts a new task if the nonneutral cache is invalid +// // This method is called from within a "single" construct. +// inline __attribute__((always_inline)) void validate_nonneutral_cache(int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const +// { +// if ((nonneutral_change_validation_ != p_nonneutral_change_counter) || (nonneutral_mutations_count_ == -1)) +// { +// // If the nonneutral change counter has changed since we last validated, or our cache is invalid for other +// // reasons (most notably being a new mutation run that has not yet cached), validate it with an OpenMP task +// // We set up these variables to prevent ourselves from seeing the cache as invalid again +// nonneutral_change_validation_ = p_nonneutral_change_counter; +// nonneutral_mutations_count_ = 0; +// +//#if (SLIMPROFILING == 1) +// // PROFILING +// recached_run_ = true; +//#endif +// +// // I tried splitting the below code out into its own non-inline method, +// // but that seemed to trigger a compiler bug, so here we are. +//#pragma omp task +// { +// switch (p_nonneutral_regime) +// { +// case 1: cache_nonneutral_mutations_REGIME_1(); break; +// case 2: cache_nonneutral_mutations_REGIME_2(); break; +// case 3: cache_nonneutral_mutations_REGIME_3(); break; +// } +// } +// } +// } +//#endif #if (SLIMPROFILING == 1) // PROFILING diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index f3ce9868..b6b2cedf 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -724,14 +724,14 @@ EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalSt { int64_t trait_index = trait_indices[0]; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(DefaultDominanceForTrait(trait_index))); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)DefaultDominanceForTrait(trait_index))); } else { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); for (int64_t trait_index : trait_indices) - float_result->push_float_no_check(DefaultDominanceForTrait(trait_index)); + float_result->push_float_no_check((double)DefaultDominanceForTrait(trait_index)); return EidosValue_SP(float_result); } @@ -752,14 +752,14 @@ EidosValue_SP MutationType::ExecuteMethod_defaultHemizygousDominanceForTrait(Eid { int64_t trait_index = trait_indices[0]; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(DefaultHemizygousDominanceForTrait(trait_index))); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)DefaultHemizygousDominanceForTrait(trait_index))); } else { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); for (int64_t trait_index : trait_indices) - float_result->push_float_no_check(DefaultHemizygousDominanceForTrait(trait_index)); + float_result->push_float_no_check((double)DefaultHemizygousDominanceForTrait(trait_index)); return EidosValue_SP(float_result); } @@ -880,7 +880,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID { int64_t trait_index = trait_indices[0]; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(DrawEffectForTrait(trait_index))); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)DrawEffectForTrait(trait_index))); } else { @@ -889,7 +889,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID // draw_index is the outer loop, so that we get num_draws sets of (one draw per trait) for (int64_t draw_index = 0; draw_index < num_draws; ++draw_index) for (int64_t trait_index : trait_indices) - float_result->push_float_no_check(DrawEffectForTrait(trait_index)); + float_result->push_float_no_check((double)DrawEffectForTrait(trait_index)); return EidosValue_SP(float_result); } diff --git a/core/polymorphism.cpp b/core/polymorphism.cpp index bc618ef6..0c61f557 100644 --- a/core/polymorphism.cpp +++ b/core/polymorphism.cpp @@ -58,7 +58,7 @@ void Polymorphism::Print_ID_Tag(std::ostream &p_out) const if (trait_index > 0) p_out << ","; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].effect_size_); // necessary precision for non-lossiness + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, (double)mut_trait_info[trait_index].effect_size_); // necessary precision for non-lossiness p_out << double_buf; } @@ -75,7 +75,7 @@ void Polymorphism::Print_ID_Tag(std::ostream &p_out) const p_out << "NAN"; else { - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, dominance); // necessary precision for non-lossiness + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, (double)dominance); // necessary precision for non-lossiness p_out << double_buf; } } @@ -119,7 +119,7 @@ void Polymorphism::Print_ID(std::ostream &p_out) const if (trait_index > 0) p_out << ","; - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, mut_trait_info[trait_index].effect_size_); // necessary precision for non-lossiness + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, (double)mut_trait_info[trait_index].effect_size_); // necessary precision for non-lossiness p_out << double_buf; } @@ -136,7 +136,7 @@ void Polymorphism::Print_ID(std::ostream &p_out) const p_out << "NAN"; else { - snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, dominance); // necessary precision for non-lossiness + snprintf(double_buf, 40, "%.*g", EIDOS_FLT_DIGS, (double)dominance); // necessary precision for non-lossiness p_out << double_buf; } } diff --git a/core/population.cpp b/core/population.cpp index 9bbf928d..6128e116 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -711,17 +711,20 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind SLiMEidosBlockType old_executing_block_type = community_.executing_block_type_; community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMateChoiceCallback; - // We start out using standard weights taken from the source subpopulation. If, when we are done handling callbacks, we are still - // using those standard weights, then we can do a draw using our fast lookup tables. Otherwise, we will do a draw the hard way. bool sex_enabled = p_subpop->sex_enabled_; double *standard_weights = (sex_enabled ? p_source_subpop->cached_male_fitness_ : p_source_subpop->cached_parental_fitness_); - double *current_weights = standard_weights; - slim_popsize_t weights_length = p_source_subpop->cached_fitness_size_; - bool weights_modified = false; Individual *chosen_mate = nullptr; // callbacks can return an Individual instead of a weights vector, held here bool weights_reflect_chosen_mate = false; // if T, a weights vector has been created with a 1 for the chosen mate, to pass to the next callback SLiMEidosBlock *last_interventionist_mate_choice_callback = nullptr; + // We start out using standard weights taken from the source subpopulation. NOTE THAT THOSE COULD BE NULLPTR, in the case where + // the fitness of all individuals is equal; if we need to, we will allocate the buffer in the source subpopulation ourselves. + // If, when we are done handling callbacks, we are still using the standard weights, then we can do a draw using our fast lookup + // tables (or equally weighted, if the weights are still nullptr). Otherwise, we will do a draw the hard way. + double *current_weights = standard_weights; + slim_popsize_t weights_length = p_source_subpop->cached_fitness_size_; + bool weights_modified = false; + for (SLiMEidosBlock *mate_choice_callback : p_mate_choice_callbacks) { if (mate_choice_callback->block_active_) @@ -806,6 +809,46 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind if (mate_choice_callback->contains_weights_) { + // current_weights could be nullptr at this point, if standard_weights was nullptr on entry. + // In that case, we need to set up standard_weights and then point to it with current_weights. + if (!current_weights) + { + standard_weights = (double *)malloc(sizeof(double) * p_source_subpop->parent_subpop_size_); // allocate a new weights vector + if (!standard_weights) + EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + + // Then fill the buffer with the appropriate values, knowing that the model is neutral. + // This code used to be in UpdateWFFitnessBuffers(); now we do it ourselves on demand. + // Note that we only generate the buffer we need -- weights for choosing a second parent. + if (sex_enabled) + { + for (slim_popsize_t female_index = 0; female_index < p_source_subpop->parent_first_male_index_; female_index++) + standard_weights[female_index] = 0; + for (slim_popsize_t male_index = p_source_subpop->parent_first_male_index_; male_index < p_source_subpop->parent_subpop_size_; male_index++) + standard_weights[male_index] = 1.0; + } + else + { + for (slim_popsize_t i = 0; i < p_source_subpop->parent_subpop_size_; i++) + standard_weights[i] = 1.0; + } + + // We then give the allocated weights buffer to the subpopulation. We do not want this + // to be a private copy; we want to allocate this buffer just once per tick if possible. + if (sex_enabled) + p_source_subpop->cached_male_fitness_ = standard_weights; + else + p_source_subpop->cached_parental_fitness_ = standard_weights; + + p_source_subpop->cached_fitness_size_ = p_source_subpop->parent_subpop_size_; + p_source_subpop->cached_fitness_capacity_ = p_source_subpop->parent_subpop_size_; + + // Then we reference that buffer with current_weights just as if it had existed on entry. + current_weights = standard_weights; + weights_length = p_source_subpop->cached_fitness_size_; + weights_modified = false; + } + local_weights_ptr = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(current_weights, weights_length)); callback_symbols.InitializeConstantSymbolEntry(gEidosID_weights, local_weights_ptr); } @@ -860,7 +903,8 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind chosen_mate = nullptr; weights_reflect_chosen_mate = false; - // a non-zero float vector must match the size of the source subpop, and provides a new set of weights for us to use + // a non-zero float vector must match the size of the source subpop, and provides a + // new set of weights for us to use; note current_weights could be nullptr here! if (!weights_modified) { current_weights = (double *)malloc(sizeof(double) * weights_length); // allocate a new weights vector @@ -1062,7 +1106,8 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind SLIM_PROFILE_BLOCK_END(community_.profile_callback_totals_[(int)(SLiMEidosBlockType::SLiMEidosMateChoiceCallback)]); #endif - // The standard behavior, with no active callbacks, is to draw a male parent using the standard fitness values + // The standard behavior, with no active callbacks, is to draw a male parent using existing fitness values. + // This will fall back on equal probabilities if the GSL discrete preproc machinery has not been set up. Eidos_RNG_State *rng_state = EIDOS_STATE_RNG(omp_get_thread_num()); return (sex_enabled ? p_source_subpop->DrawMaleParentUsingFitness(rng_state) : p_source_subpop->DrawParentUsingFitness(rng_state)); @@ -5046,7 +5091,7 @@ void Population::RecordFitness(slim_tick_t p_history_index, slim_objectid_t p_su // Assuming we now have a record, resize it as needed and insert the new value if (history_rec_ptr) { - double *history = history_rec_ptr->history_; + double *history = history_rec_ptr->history_; // FIXME MULTITRAIT: this should be changed to slim_fitness_t, and in QtSLiM also slim_tick_t history_length = history_rec_ptr->history_length_; if (p_history_index >= history_length) @@ -5057,7 +5102,7 @@ void Population::RecordFitness(slim_tick_t p_history_index, slim_objectid_t p_su history = (double *)realloc(history, history_length * sizeof(double)); for (slim_tick_t i = oldHistoryLength; i < history_length; ++i) - history[i] = NAN; + history[i] = std::numeric_limits::quiet_NaN(); // Copy the new values back into the history record history_rec_ptr->history_ = history; @@ -5140,7 +5185,7 @@ void Population::SurveyPopulation(void) double subpop_unscaled_total = 0; for (Individual *individual : subpop->parent_individuals_) - subpop_unscaled_total += individual->cached_unscaled_fitness_; + subpop_unscaled_total += (double)individual->cached_unscaled_fitness_; totalUnscaledFitness += subpop_unscaled_total; totalPopSize += subpop_size; @@ -5373,19 +5418,27 @@ void Population::RecalculateFitness(slim_tick_t p_tick) } } + // we need to recalculate phenotypes for traits that have a direct effect on fitness + std::vector p_direct_effect_trait_indices; + const std::vector &traits = species_.Traits(); + + for (int trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) + if (traits[trait_index]->HasDirectFitnessEffect()) + p_direct_effect_trait_indices.push_back(trait_index); + // move forward to the regime we just chose; UpdateFitness() can consult this to get the current regime species_.last_nonneutral_regime_ = current_regime; SLiMEidosBlockType old_executing_block_type = community_.executing_block_type_; community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMutationEffectCallback; // used for both mutationEffect() and fitnessEffect() for simplicity - // FIXME this will get cleaned up when multiple phenotypes is done - + // FIXME MULTITRAIT: this will get cleaned up when multiple phenotypes is done + // call UpdateFitness() for each subpopulation if (no_active_callbacks) { std::vector no_callbacks; for (std::pair &subpop_pair : subpops_) - subpop_pair.second->UpdateFitness(no_callbacks, no_callbacks); + subpop_pair.second->UpdateFitness(no_callbacks, no_callbacks, p_direct_effect_trait_indices); } else { @@ -5393,7 +5446,7 @@ void Population::RecalculateFitness(slim_tick_t p_tick) { slim_objectid_t subpop_id = subpop_pair.first; Subpopulation *subpop = subpop_pair.second; - std::vector subpop_mutationEffect_callbacks; // FIXME MULTITRAIT won't need this any more + std::vector subpop_mutationEffect_callbacks; std::vector subpop_fitnessEffect_callbacks; // Get mutationEffect() and fitnessEffect() callbacks that apply to this subpopulation @@ -5413,7 +5466,7 @@ void Population::RecalculateFitness(slim_tick_t p_tick) } // Update fitness values, using the callbacks - subpop->UpdateFitness(subpop_mutationEffect_callbacks, subpop_fitnessEffect_callbacks); + subpop->UpdateFitness(subpop_mutationEffect_callbacks, subpop_fitnessEffect_callbacks, p_direct_effect_trait_indices); } } @@ -5435,6 +5488,11 @@ void Population::RecalculateFitness(slim_tick_t p_tick) individual->fitness_scaling_ = 1.0; } } + + // FIXME MULTITRAIT: at present we can't clear this flag here because it is shared by all species. That + // means that if any species uses individual fitnessScaling, all of them take a performance hit. I think + // we could move this flag to Subpopulation or Species, but that might add overhead in tracking it... + //Individual::s_any_individual_fitness_scaling_set_ = false; } #if SLIM_CLEAR_HAPLOSOMES diff --git a/core/slim_globals.h b/core/slim_globals.h index b6ca41ed..92e1aa7a 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -125,6 +125,7 @@ typedef int64_t slim_pedigreeid_t; // identifiers for pedigreed individuals; ov typedef int64_t slim_haplosomeid_t; // identifiers for pedigreed haplosomes; not user-visible, used by the tree-recording code, pedigree_id*2 + [0/1] typedef int32_t slim_polymorphismid_t; // identifiers for polymorphisms, which need only 32 bits since they are only segregating mutations typedef float slim_effect_t; // storage of trait effects (e.g., selection coefficients) in memory-tight classes; also dominance coefficients +typedef float slim_fitness_t; // storage of fitness effects (e.g., fitnessScaling values) and final individual fitness values #define SLIM_MAX_TICK (1000000000L) // ticks range from 0 (init time) to this; SLIM_MAX_TICK + 1 is an "infinite" marker value #define SLIM_MAX_BASE_POSITION (1000000000000000L) // base positions in the chromosome can range from 0 to 1e15; see above diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 2c102c6e..2186d069 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1025,8 +1025,6 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.directFitnessEffect, F)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.directFitnessEffect, F)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.directFitnessEffect = T; if (!identical(T_height.directFitnessEffect, T)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.directFitnessEffect = T; if (!identical(T_weight.directFitnessEffect, T)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.index, 0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.index, 1)) stop(); }"); diff --git a/core/spatial_map.cpp b/core/spatial_map.cpp index 22e25fab..36c185e5 100644 --- a/core/spatial_map.cpp +++ b/core/spatial_map.cpp @@ -732,14 +732,14 @@ void SpatialMap::ColorForValue(double p_value, double *p_rgb_ptr) if (color_index_2 >= n_colors_) color_index_2 = n_colors_ - 1; double color_2_weight = color_index - color_index_1; - double color_1_weight = 1.0F - color_2_weight; + double color_1_weight = 1.0 - color_2_weight; - double red1 = red_components_[color_index_1]; - double green1 = green_components_[color_index_1]; - double blue1 = blue_components_[color_index_1]; - double red2 = red_components_[color_index_2]; - double green2 = green_components_[color_index_2]; - double blue2 = blue_components_[color_index_2]; + double red1 = (double)red_components_[color_index_1]; + double green1 = (double)green_components_[color_index_1]; + double blue1 = (double)blue_components_[color_index_1]; + double red2 = (double)red_components_[color_index_2]; + double green2 = (double)green_components_[color_index_2]; + double blue2 = (double)blue_components_[color_index_2]; p_rgb_ptr[0] = (red1 * color_1_weight + red2 * color_2_weight); p_rgb_ptr[1] = (green1 * color_1_weight + green2 * color_2_weight); @@ -773,14 +773,14 @@ void SpatialMap::ColorForValue(double p_value, float *p_rgb_ptr) if (color_index_2 >= n_colors_) color_index_2 = n_colors_ - 1; double color_2_weight = color_index - color_index_1; - double color_1_weight = 1.0F - color_2_weight; - - double red1 = red_components_[color_index_1]; - double green1 = green_components_[color_index_1]; - double blue1 = blue_components_[color_index_1]; - double red2 = red_components_[color_index_2]; - double green2 = green_components_[color_index_2]; - double blue2 = blue_components_[color_index_2]; + double color_1_weight = 1.0 - color_2_weight; + + double red1 = (double)red_components_[color_index_1]; + double green1 = (double)green_components_[color_index_1]; + double blue1 = (double)blue_components_[color_index_1]; + double red2 = (double)red_components_[color_index_2]; + double green2 = (double)green_components_[color_index_2]; + double blue2 = (double)blue_components_[color_index_2]; p_rgb_ptr[0] = (float)(red1 * color_1_weight + red2 * color_2_weight); p_rgb_ptr[1] = (float)(green1 * color_1_weight + green2 * color_2_weight); diff --git a/core/species.cpp b/core/species.cpp index af50f2ae..c66010a1 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1419,7 +1419,7 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos #endif // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_neutral_mutations_ - if (selection_coeff != 0.0) + if (selection_coeff != (slim_effect_t)0.0) { pure_neutral_ = false; mutation_type_ptr->all_neutral_mutations_ = false; @@ -1676,6 +1676,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid int32_t double_size; double double_test; int64_t flags = 0; + // FIXME MULTITRAIT: add new sizes here like slim_fitness_t int32_t slim_tick_t_size, slim_position_t_size, slim_objectid_t_size, slim_popsize_t_size, slim_refcount_t_size, slim_effect_t_size, slim_mutationid_t_size, slim_polymorphismid_t_size, slim_age_t_size, slim_pedigreeid_t_size, slim_haplosomeid_t_size, slim_usertag_t_size; int header_length = sizeof(double_size) + sizeof(double_test) + sizeof(flags) + sizeof(slim_tick_t_size) + sizeof(slim_position_t_size) + sizeof(slim_objectid_t_size) + sizeof(slim_popsize_t_size) + sizeof(slim_refcount_t_size) + sizeof(slim_effect_t_size) + sizeof(slim_mutationid_t_size) + sizeof(slim_polymorphismid_t_size) + sizeof(slim_age_t_size) + sizeof(slim_pedigreeid_t_size) + sizeof(slim_haplosomeid_t_size) + sizeof(slim_usertag_t_size) + sizeof(file_tick) + sizeof(file_cycle) + sizeof(section_end_tag); @@ -2185,7 +2186,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid #endif // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_neutral_mutations_ - if (selection_coeff != 0.0) + if (selection_coeff != (slim_effect_t)0.0) { pure_neutral_ = false; mutation_type_ptr->all_neutral_mutations_ = false; @@ -2534,6 +2535,14 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMutationEffectCallback; // used for both mutationEffect() and fitnessEffect() for simplicity community_.executing_species_ = this; + // we need to recalculate phenotypes for traits that have a direct effect on fitness + std::vector p_direct_effect_trait_indices; + const std::vector &traits = Traits(); + + for (int trait_index = 0; trait_index < TraitCount(); ++trait_index) + if (traits[trait_index]->HasDirectFitnessEffect()) + p_direct_effect_trait_indices.push_back(trait_index); + for (std::pair &subpop_pair : population_.subpops_) { slim_objectid_t subpop_id = subpop_pair.first; @@ -2541,7 +2550,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid std::vector mutationEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, subpop_id, -1, -1); std::vector fitnessEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, subpop_id, -1, -1); - subpop->UpdateFitness(mutationEffect_callbacks, fitnessEffect_callbacks); + subpop->UpdateFitness(mutationEffect_callbacks, fitnessEffect_callbacks, p_direct_effect_trait_indices); } community_.executing_block_type_ = old_executing_block_type; @@ -4479,9 +4488,9 @@ void Species::TabulateSLiMMemoryUsage_Species(SLiMMemoryUsage_Species *p_usage) /* Subpopulation: - gsl_ran_discrete_t *lookup_parent_ = nullptr; // OWNED POINTER: lookup table for drawing a parent based upon fitness - gsl_ran_discrete_t *lookup_female_parent_ = nullptr; // OWNED POINTER: lookup table for drawing a female parent based upon fitness, SEX ONLY - gsl_ran_discrete_t *lookup_male_parent_ = nullptr; // OWNED POINTER: lookup table for drawing a male parent based upon fitness, SEX ONLY + gsl_ran_discrete_t *lookup_parent_ = nullptr; + gsl_ran_discrete_t *lookup_female_parent_ = nullptr; + gsl_ran_discrete_t *lookup_male_parent_ = nullptr; */ @@ -7848,7 +7857,7 @@ void Species::MetadataForMutation(Mutation *p_mutation, MutationMetadataRec *p_m p_metadata->mutation_type_id_ = p_mutation->mutation_type_ptr_->mutation_type_id_; - // FIXME MULTITRAIT: We need to figure out where we're going to multitrait information in .trees + // FIXME MULTITRAIT: We need to figure out where we're going to put multitrait information in .trees // For now we just write out the effect for trait 0, but we need the dominance coeff too, and we need // it for all traits in the model not just trait 0; this design is not going to work. See // https://github.com/MesserLab/SLiM/issues/569 @@ -7871,7 +7880,7 @@ void Species::MetadataForSubstitution(Substitution *p_substitution, MutationMeta p_metadata->mutation_type_id_ = p_substitution->mutation_type_ptr_->mutation_type_id_; - // FIXME MULTITRAIT: We need to figure out where we're going to multitrait information in .trees + // FIXME MULTITRAIT: We need to figure out where we're going to put multitrait information in .trees // For now we just write out the effect for trait 0, but we need the dominance coeff too, and we need // it for all traits in the model not just trait 0; this design is not going to work. See // https://github.com/MesserLab/SLiM/issues/569 @@ -9947,7 +9956,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapall_neutral_mutations_ = false; diff --git a/core/species.h b/core/species.h index b78d0e51..6e15bb58 100644 --- a/core/species.h +++ b/core/species.h @@ -100,7 +100,7 @@ enum class SLiMFileFormat typedef struct __attribute__((__packed__)) { slim_objectid_t mutation_type_id_; // 4 bytes (int32_t): the id of the mutation type the mutation belongs to slim_effect_t selection_coeff_; // 4 bytes (float): the mutation effect (e.g., selection coefficient) - // FIXME MULTITRAIT need to add a dominance_coeff_ property here! + // FIXME MULTITRAIT need to add a dominance_coeff_ property here! and hemizygous_dominance_coeff_! slim_objectid_t subpop_index_; // 4 bytes (int32_t): the id of the subpopulation in which the mutation arose slim_tick_t origin_tick_; // 4 bytes (int32_t): the tick in which the mutation arose int8_t nucleotide_; // 1 byte (int8_t): the nucleotide for the mutation (0='A', 1='C', 2='G', 3='T'), or -1 diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index fa3adf4a..eee514aa 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1694,8 +1694,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires baselineOffset to be representable as a finite single-precision floating-point number; the value given rounded to infinity." << EidosTerminate(); } - if ((type == TraitType::kMultiplicative) && (baselineOffset < 0.0)) - baselineOffset = 0.0; + if ((type == TraitType::kMultiplicative) && (baselineOffset < (slim_effect_t)0.0)) + baselineOffset = (slim_effect_t)0.0; // check that the default distribution is used or not used, in its entirety if (individualOffsetMean_value->Type() != individualOffsetSD_value->Type()) @@ -1925,7 +1925,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string std::ostream &output_stream = p_interpreter.ExecutionOutputStream(); output_stream << "initializeTrait(name='" << name << "', type='" << type_string << "'"; - if (baselineOffset != 0.0) + if (baselineOffset != (slim_effect_t)0.0) output_stream << ", baselineOffset=" << baselineOffset << ""; if (individualOffsetMean != 0.0) output_stream << ", individualOffsetMean=" << individualOffsetMean << ""; diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 0e345858..e7de650b 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1152,20 +1152,11 @@ Subpopulation::Subpopulation(Population &p_population, slim_objectid_t p_subpopu if (model_type_ == SLiMModelType::kModelTypeWF) { - // Set up to draw random individuals, based initially on equal fitnesses - cached_parental_fitness_ = (double *)realloc(cached_parental_fitness_, sizeof(double) * parent_subpop_size_); - if (!cached_parental_fitness_) - EIDOS_TERMINATION << "ERROR (Subpopulation::Subpopulation): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - - cached_fitness_capacity_ = parent_subpop_size_; - cached_fitness_size_ = parent_subpop_size_; - - double *fitness_buffer_ptr = cached_parental_fitness_; - - for (slim_popsize_t i = 0; i < parent_subpop_size_; i++) - *(fitness_buffer_ptr++) = 1.0; - - lookup_parent_ = gsl_ran_discrete_preproc(parent_subpop_size_, cached_parental_fitness_); + // Set up to draw random individuals, based initially on equal fitnesses. This flag overrides all + // individual fitness values, instead considering them all to be 1.0. It also avoids the overhead + // of setting for GSL discrete preproc drawing of parents; instead we will draw with equal probability. + individual_cached_fitness_OVERRIDE_ = true; + individual_cached_fitness_OVERRIDE_value_ = 1.0; } } @@ -1203,35 +1194,11 @@ Subpopulation::Subpopulation(Population &p_population, slim_objectid_t p_subpopu if (model_type_ == SLiMModelType::kModelTypeWF) { - // Set up to draw random females, based initially on equal fitnesses - cached_parental_fitness_ = (double *)realloc(cached_parental_fitness_, sizeof(double) * parent_subpop_size_); - cached_male_fitness_ = (double *)realloc(cached_male_fitness_, sizeof(double) * parent_subpop_size_); - if (!cached_parental_fitness_ || !cached_male_fitness_) - EIDOS_TERMINATION << "ERROR (Subpopulation::Subpopulation): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - - cached_fitness_capacity_ = parent_subpop_size_; - cached_fitness_size_ = parent_subpop_size_; - - double *fitness_buffer_ptr = cached_parental_fitness_; - double *male_buffer_ptr = cached_male_fitness_; - - for (slim_popsize_t i = 0; i < parent_first_male_index_; i++) - { - *(fitness_buffer_ptr++) = 1.0; - *(male_buffer_ptr++) = 0.0; // this vector has 0 for all females, for mateChoice() callbacks - } - - // Set up to draw random males, based initially on equal fitnesses - slim_popsize_t num_males = parent_subpop_size_ - parent_first_male_index_; - - for (slim_popsize_t i = 0; i < num_males; i++) - { - *(fitness_buffer_ptr++) = 1.0; - *(male_buffer_ptr++) = 1.0; - } - - lookup_female_parent_ = gsl_ran_discrete_preproc(parent_first_male_index_, cached_parental_fitness_); - lookup_male_parent_ = gsl_ran_discrete_preproc(num_males, cached_parental_fitness_ + parent_first_male_index_); + // Set up to draw random individuals, based initially on equal fitnesses. This flag overrides all + // individual fitness values, instead considering them all to be 1.0. It also avoids the overhead + // of setting for GSL discrete preproc drawing of parents; instead we will draw with equal probability. + individual_cached_fitness_OVERRIDE_ = true; + individual_cached_fitness_OVERRIDE_value_ = 1.0; } if (model_type_ == SLiMModelType::kModelTypeNonWF) @@ -1268,6 +1235,9 @@ Subpopulation::~Subpopulation(void) if (cached_male_fitness_) free(cached_male_fitness_); + cached_fitness_size_ = 0; + cached_fitness_capacity_ = 0; + { // dispose of haplosomes and individuals with our object pools // note that these might get reused; this is not necessarily the simulation end @@ -1381,1032 +1351,381 @@ void Subpopulation::FixNonNeutralCaches_OMP(void) } #endif -void Subpopulation::UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks) +// Population::RecalculateFitness() figures out which callbacks are relevant for each subpopulation, and which +// traits need to be evaluated in order to calculate fitness (only with a direct fitness effect). It then +// calls UpdateFitness() on each subpopulation. This method expresses demand for the traits in question, and +// then produces fitness values by factoring in fitnessEffect() callbacks and fitnessScaling values. It stores +// the fitness values in the appropriate places to prepare for their later use. +void Subpopulation::UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) { - const std::map &mut_types = species_.MutationTypes(); - - // This function calculates the population mean fitness as a side effect - double totalFitness = 0.0; + // Determine whether we are in a "pure neutral" case where we don't need to calculate individual fitness + // because all individuals have neutral fitness. The simplest case where this is true is if there are no + // traits with a direct fitness effect, no non-neutral subpopulation or individual fitnessScaling effects, + // and no active mutationEffect() or fitnessEffect() callbacks. There are more subtle ways it can be true + // also: active callbacks are OK as long as they return a constant, neutral value; and traits with a direct + // fitness effect are OK as long as either (a) all mutations have a neutral effect upon that particular trait, + // or (b) the trait value doesn't matter because it is overridden by an active mutationEffect() callback + // with a constant, neutral value. We evaluate this for each subpopulation because different subpopulations + // can have different active callbacks, and because the callbacks in other subpopulations might active or + // deactivate the callbacks in this subpopulation; we therefore need to figure it out right here at this + // moment. Even if we decide that we are not "pure neutral", we might deduce that a particular trait with a + // direct fitness effect is not relevant to fitness in this subpopulation, for the above reasons; in that + // case, we still want to remove it from the set of traits that we demand and evaluate, for efficiency. And + // we might decide that a given trait is relevant to fitness, but has a constant effect for all individuals; + // we then want to remove that trait, but factor that constant effect in. + slim_fitness_t subpop_fitness_scaling = (slim_fitness_t)subpop_fitness_scaling_; // guaranteed >= 0.0 + bool has_constant_fitness = true; +#ifdef SLIMGUI + double constant_unscaled_fitness_value = 1.0; // without subpopulation fitnessScaling +#endif + double constant_fitness_value = (double)subpop_fitness_scaling; // with all fitness effects incorporated - // Figure out our callback scenario: zero, one, or many? See the comment below, above FitnessOfParentWithHaplosomeIndices_NoCallbacks(), - // for more info on this complication. Here we just figure out which version to call and set up for it. - int mutationEffect_callback_count = (int)p_mutationEffect_callbacks.size(); - bool mutationEffect_callbacks_exist = (mutationEffect_callback_count > 0); - bool single_mutationEffect_callback = false; + if (true) + has_constant_fitness = false; // for now, assume this is false since we don't know - if (mutationEffect_callback_count == 1) + if (has_constant_fitness) { - slim_objectid_t mutation_type_id = p_mutationEffect_callbacks[0]->mutation_type_id_; - MutationType *found_muttype = species_.MutationTypeWithID(mutation_type_id); + // we know this subpopulation has effectively constant fitness; we therefore don't express demand for + // any traits, which means trait may keep NAN values even if the traits have a direct fitness effect - if (found_muttype) - { - if (mut_types.size() > 1) - { - // We have a single callback that applies to a known mutation type among more than one defined type; we can optimize that - single_mutationEffect_callback = true; - } - // else there is only one mutation type, so the callback applies to all mutations in the simulation - } - else + if (model_type_ == SLiMModelType::kModelTypeWF) { - // The only callback refers to a mutation type that doesn't exist, so we effectively have no callbacks; we probably never hit this - mutationEffect_callback_count = 0; - (void)mutationEffect_callback_count; // tell the static analyzer that we know we just did a dead store - mutationEffect_callbacks_exist = false; + // in WF models we can take advantage of constant fitness to completely remove individual-level + // fitness bookkeeping with the individual_cached_fitness_OVERRIDE_ mechanism + individual_cached_fitness_OVERRIDE_ = true; + individual_cached_fitness_OVERRIDE_value_ = constant_fitness_value; + +#ifdef SLIMGUI + for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) + parent_individuals_[individual_index]->cached_unscaled_fitness_ = (slim_effect_t)constant_unscaled_fitness_value; +#endif } - } - - // Can we skip chromosome-based fitness calculations altogether, and just call fitnessEffect() callbacks if any? - // We can do this if (a) all mutation types either use a neutral DES, or have been made neutral with a "return 1.0;" - // mutationEffect() callback that is active, (b) for the mutation types that use a neutral DES, no mutation has had its - // selection coefficient changed, and (c) no mutationEffect() callbacks are active apart from "return 1.0;" type callbacks. - // This is often the case for QTL-based models (such as Misha's coral model), and should produce a big speed gain, - // so we do a pre-check here for this case. Note that we can ignore fitnessEffect() callbacks in this situation, - // because they are explicitly documented as potentially being executed after mutationEffect() callbacks, so - // they are not allowed, as a matter of policy, to alter the operation of mutationEffect() callbacks. - bool skip_chromosomal_fitness = true; - - // Looping through all of the mutation types and setting flags can be very expensive, so as a first pass we check - // whether it is even conceivable that we will be able to have skip_chromosomal_fitness == true. If the simulation - // is not pure neutral and we have no mutationEffect() callback that could change that, it is a no-go without checking the - // mutation types at all. - if (!species_.pure_neutral_) - { - skip_chromosomal_fitness = false; // we're not pure neutral, so we have to prove that it is possible - - for (SLiMEidosBlock *mutationEffect_callback : p_mutationEffect_callbacks) + else // (model_type_ == SLiMModelType::kModelTypeNonWF) { - if (mutationEffect_callback->block_active_) + // in nonWF models we are required to fill in the per-individual fitness values, but do little else + individual_cached_fitness_OVERRIDE_ = false; + + for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) { - const EidosASTNode *compound_statement_node = mutationEffect_callback->compound_statement_node_; + Individual *ind = parent_individuals_[individual_index]; - if (compound_statement_node->cached_return_value_) - { - // The script is a constant expression such as "{ return 1.1; }" - EidosValue *result = compound_statement_node->cached_return_value_.get(); +#ifdef SLIMGUI + ind->cached_unscaled_fitness_ = (slim_effect_t)constant_unscaled_fitness_value; +#endif - if ((result->Type() == EidosValueType::kValueFloat) || (result->Count() == 1)) - { - if (result->FloatData()[0] == 1.0) - { - // we have a mutationEffect() callback that is neutral-making, so it could conceivably work; - // change our minds but keep checking - skip_chromosomal_fitness = true; - continue; - } - } - } - - // if we reach this point, we have an active callback that is not neutral-making, so we fail and we're done - skip_chromosomal_fitness = false; - break; + ind->cached_fitness_UNSAFE_ = (slim_effect_t)constant_fitness_value; } } } - - // At this point it appears conceivable that we could skip, but we don't yet know. We need to do a more thorough - // check, actually tracking precisely which mutation types are neutral and non-neutral, and which are made neutral - // by mutationEffect() callbacks. Note this block is the only place where is_pure_neutral_now_ is valid or used!!! - if (skip_chromosomal_fitness) + else { - // first set a flag on all mut types indicating whether they are neutral according to their mutations - // this is the one place where all_neutral_mutations_ is actually used in the current design, because - // mutationEffect() callbacks target mutation types -- we want to know if all of the non-neutral - // mutation types have been made neutral by a mutationEffect() callback. - for (auto &mut_type_iter : mut_types) - mut_type_iter.second->is_pure_neutral_now_ = mut_type_iter.second->all_neutral_mutations_; - - // then go through the mutationEffect() callback list and set the pure neutral flag for mut types neutralized by an active callback - for (SLiMEidosBlock *mutationEffect_callback : p_mutationEffect_callbacks) - { - if (mutationEffect_callback->block_active_) - { - const EidosASTNode *compound_statement_node = mutationEffect_callback->compound_statement_node_; - - if (compound_statement_node->cached_return_value_) - { - // The script is a constant expression such as "{ return 1.1; }" - EidosValue *result = compound_statement_node->cached_return_value_.get(); - - if ((result->Type() == EidosValueType::kValueFloat) && (result->Count() == 1)) - { - if (result->FloatData()[0] == 1.0) - { - // the callback returns 1.0, so it makes the mutation types to which it applies become neutral - slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; - - if (mutation_type_id == -1) - { - for (auto &mut_type_iter : mut_types) - mut_type_iter.second->is_pure_neutral_now_ = true; - } - else - { - MutationType *found_muttype = species_.MutationTypeWithID(mutation_type_id); - - if (found_muttype) - found_muttype->is_pure_neutral_now_ = true; - } - - continue; - } - } + // we cannot override individual cached fitness values; individuals are not all neutral fitness + individual_cached_fitness_OVERRIDE_ = false; + + // demand phenotypes for all the relevant traits + if (p_direct_effect_trait_indices.size()) +#warning make a new DemandPhenotype() function for whole subpops +#warning need to think about shuffling the order for DemandPhenotype as well! + gSLiM_Individual_Class->DemandPhenotype(&species_, parent_individuals_.data(), (int)parent_individuals_.size(), p_direct_effect_trait_indices); // FIXME MULTITRAIT: pass in p_mutationEffect_callbacks to a per-subpop version of this + + // then loop over individuals and pull together the relevant phenotype values, fitnessEffect() callbacks, + // subpopulation fitnessScaling, and individual fitnessScaling to produce final individual fitness values; + // we choose our _UpdateFitness() template based upon flags and execute it to calculate fitness values + bool f_has_subpop_fitnessScaling = (subpop_fitness_scaling != 1.0f); + bool f_has_ind_fitnessScaling = Individual::s_any_individual_fitness_scaling_set_; + bool f_has_fitnessEffect_callbacks = (p_fitnessEffect_callbacks.size() > 0); + bool f_has_trait_direct_effects = (p_direct_effect_trait_indices.size() > 0); + bool f_single_trait = (p_direct_effect_trait_indices.size() == 1); + void (Subpopulation::*_UpdateFitness_TEMPLATED)(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) = nullptr; + + if (f_has_subpop_fitnessScaling) + { + if (f_has_ind_fitnessScaling) { + if (f_has_fitnessEffect_callbacks) { + if (f_has_trait_direct_effects) { + if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else { + if (f_has_trait_direct_effects) { + if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } + } else { + if (f_has_fitnessEffect_callbacks) { + if (f_has_trait_direct_effects) { + if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else { + if (f_has_trait_direct_effects) { + if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; } - - // if we reach this point, we have an active callback that is not neutral-making, so we fail and we're done - skip_chromosomal_fitness = false; - break; } - } - - // finally, tabulate the pure-neutral flags of all the mut types into an overall flag for whether we can skip - if (skip_chromosomal_fitness) - { - for (auto &mut_type_iter : mut_types) - { - if (!mut_type_iter.second->is_pure_neutral_now_) - { - skip_chromosomal_fitness = false; - break; + } else { + if (f_has_ind_fitnessScaling) { + if (f_has_fitnessEffect_callbacks) { + if (f_has_trait_direct_effects) { + if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else { + if (f_has_trait_direct_effects) { + if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } + } else { + if (f_has_fitnessEffect_callbacks) { + if (f_has_trait_direct_effects) { + if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else { + if (f_has_trait_direct_effects) { + if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; } } } - else - { - // At this point, there is an active callback that is not neutral-making, so we really can't reliably depend - // upon is_pure_neutral_now_; that rogue callback could make other callbacks active/inactive, etc. So in - // principle we should now go through and clear the is_pure_neutral_now_ flags to avoid any confusion. But - // we are the only ones to use is_pure_neutral_now_, and we're done using it, so we can skip that work. - - //for (auto &mut_type_iter : mut_types) - // mut_type_iter.second->is_pure_neutral_now_ = false; - } - } - - // Figure out global callbacks; these are callbacks with NULL supplied for the mut-type id, which means that they are called - // exactly once per individual, for every individual regardless of genetics, to provide an entry point for alternate fitness definitions - int fitnessEffect_callback_count = (int)p_fitnessEffect_callbacks.size(); - bool fitnessEffect_callbacks_exist = (fitnessEffect_callback_count > 0); - - // We optimize the pure neutral case, as long as no mutationEffect() or fitnessEffect() callbacks are defined; fitness values are then simply 1.0, for everybody. - // BCH 12 Jan 2018: now fitness_scaling_ modifies even pure_neutral_ models, but the framework here remains valid - bool pure_neutral = (!mutationEffect_callbacks_exist && !fitnessEffect_callbacks_exist && species_.pure_neutral_); - double subpop_fitness_scaling = subpop_fitness_scaling_; - - // Reset our override of individual cached fitness values; we make this decision afresh with each UpdateFitness() call. See - // the header for further comments on this mechanism. - individual_cached_fitness_OVERRIDE_ = false; - - // Decide whether we need to shuffle the order of operations. This occurs only if (a) we have mutationEffect() or fitnessEffect() callbacks - // that are enabled, and (b) at least one of them has no cached optimization set. Otherwise, the order of operations doesn't matter. - bool needs_shuffle = false; - - if (species_.RandomizingCallbackOrder()) - { - if (!needs_shuffle) - for (SLiMEidosBlock *callback : p_fitnessEffect_callbacks) - if (!callback->compound_statement_node_->cached_return_value_ && !callback->has_cached_optimization_) - { - needs_shuffle = true; - break; - } - if (!needs_shuffle) - for (SLiMEidosBlock *callback : p_mutationEffect_callbacks) - if (!callback->compound_statement_node_->cached_return_value_ && !callback->has_cached_optimization_) - { - needs_shuffle = true; - break; - } + (this->*(_UpdateFitness_TEMPLATED))(p_fitnessEffect_callbacks, p_direct_effect_trait_indices); } - // determine the templated version of FitnessOfParent() that we will call out to for fitness evaluation - // see Population::EvolveSubpopulation() for further comments on this optimization technique - bool mutrun_exp_timing_per_individual = species_.DoingAnyMutationRunExperiments() && (species_.Chromosomes().size() > 1); - double (Subpopulation::*FitnessOfParent_TEMPLATED)(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); - - if (mutrun_exp_timing_per_individual) - { - // If *any* chromosome is doing mutrun experiments, we can't template them out - if (!mutationEffect_callbacks_exist) - FitnessOfParent_TEMPLATED = &Subpopulation::FitnessOfParent; - else if (single_mutationEffect_callback) - FitnessOfParent_TEMPLATED = &Subpopulation::FitnessOfParent; - else - FitnessOfParent_TEMPLATED = &Subpopulation::FitnessOfParent; - } - else - { - if (!mutationEffect_callbacks_exist) - FitnessOfParent_TEMPLATED = &Subpopulation::FitnessOfParent; - else if (single_mutationEffect_callback) - FitnessOfParent_TEMPLATED = &Subpopulation::FitnessOfParent; - else - FitnessOfParent_TEMPLATED = &Subpopulation::FitnessOfParent; - } - - // refine the above choice with a custom version of optimizations for simple cases - if (!mutrun_exp_timing_per_individual && !mutationEffect_callbacks_exist && (species_.Chromosomes().size() == 1)) - { - Chromosome *chromosome = species_.Chromosomes()[0]; - - switch (chromosome->Type()) - { - // diploid, possibly with one or both being null haplosomes - case ChromosomeType::kA_DiploidAutosome: - case ChromosomeType::kX_XSexChromosome: - case ChromosomeType::kZ_ZSexChromosome: - FitnessOfParent_TEMPLATED = &Subpopulation::FitnessOfParent_1CH_Diploid; - break; - - // haploid, possibly null - case ChromosomeType::kH_HaploidAutosome: - case ChromosomeType::kY_YSexChromosome: - case ChromosomeType::kW_WSexChromosome: - case ChromosomeType::kHF_HaploidFemaleInherited: - case ChromosomeType::kFL_HaploidFemaleLine: - case ChromosomeType::kHM_HaploidMaleInherited: - case ChromosomeType::kML_HaploidMaleLine: - FitnessOfParent_TEMPLATED = &Subpopulation::FitnessOfParent_1CH_Haploid; - break; - - default: - break; - } - } + if (model_type_ == SLiMModelType::kModelTypeWF) + UpdateWFFitnessBuffers(); +} + +template +void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) +{ + // manage the shuffle buffer; this is not quite as fast as templatizing this flag, but it's simpler, and it + // only adds overhead when fitnessEffect() callbacks are present, otherwise it get optimized out completely + const bool f_has_shuffle_buffer = (f_has_fitnessEffect_callbacks && species_.RandomizingCallbackOrder()); + slim_popsize_t *shuffle_buf = (f_has_shuffle_buffer ? species_.BorrowShuffleBuffer(parent_subpop_size_) : nullptr); - // Mutrun experiment timing can be per-individual, per-chromosome, but that entails a lot of timing overhead. - // To avoid that overhead, in single-chromosome models we just time across the whole round of fitness evals - // instead. Note that in this case we chose a template above for FitnessOfParent() that does not time. - // FIXME 4/14/2025: It remains true that in multi-chrom models the timing overhead will be very high. There - // are various ways that could potentially be cut down. (a) not measure in every tick, (b) stop measuring - // once you've settled down into stasis, (c) measure a subset of all fitness evals. This should be done in - // future, but we're out of time for now. - if (species_.DoingAnyMutationRunExperiments() && (species_.Chromosomes().size() == 1)) - species_.Chromosomes()[0]->StartMutationRunExperimentClock(); + slim_fitness_t subpop_fitness_scaling = (f_has_subpop_fitnessScaling ? (slim_fitness_t)subpop_fitness_scaling_ : (slim_fitness_t)0.0); // guaranteed >= 0.0 + int64_t single_trait_index = (f_has_trait_effects && f_single_trait ? p_direct_effect_trait_indices[0] : 0); - // calculate fitnesses in parent population and cache the values - if (sex_enabled_) + for (slim_popsize_t shuffle_index = 0; shuffle_index < parent_subpop_size_; shuffle_index++) { - // SEX ONLY - double totalMaleFitness = 0.0, totalFemaleFitness = 0.0; + slim_popsize_t individual_index = (f_has_shuffle_buffer ? shuffle_buf[shuffle_index] : shuffle_index); + Individual *ind = parent_individuals_[individual_index]; + slim_fitness_t fitness = f_has_ind_fitnessScaling ? (slim_fitness_t)ind->fitness_scaling_ : (slim_fitness_t)1.0; // guaranteed >= 0.0 - // Set up to draw random females - if (pure_neutral) + if (!f_has_ind_fitnessScaling || (fitness > (slim_fitness_t)0.0)) { - if (Individual::s_any_individual_fitness_scaling_set_) - { - EIDOS_BENCHMARK_START(EidosBenchmarkType::k_FITNESS_SEX_1); - EIDOS_THREAD_COUNT(gEidos_OMP_threads_FITNESS_SEX_1); -#pragma omp parallel for schedule(static) default(none) shared(parent_subpop_size_) firstprivate(subpop_fitness_scaling) reduction(+: totalFemaleFitness) if(parent_subpop_size_ >= EIDOS_OMPMIN_FITNESS_SEX_1) num_threads(thread_count) - for (slim_popsize_t female_index = 0; female_index < parent_first_male_index_; female_index++) - { - double fitness = parent_individuals_[female_index]->fitness_scaling_; - -#ifdef SLIMGUI - parent_individuals_[female_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - parent_individuals_[female_index]->cached_fitness_UNSAFE_ = fitness; - totalFemaleFitness += fitness; - } - EIDOS_BENCHMARK_END(EidosBenchmarkType::k_FITNESS_SEX_1); - } - else + // fitness is > 0.0, so continue calculating + + if (f_has_trait_effects) { -#ifdef SLIMGUI - for (slim_popsize_t female_index = 0; female_index < parent_first_male_index_; female_index++) - parent_individuals_[female_index]->cached_unscaled_fitness_ = 1.0; -#endif + IndividualTraitInfo *trait_info = ind->trait_info_; - double fitness = subpop_fitness_scaling; // no individual fitness_scaling_ - - // Here we override setting up every cached_fitness_UNSAFE_ value, and set up a subpop-level cache instead. - // This is why cached_fitness_UNSAFE_ is marked "UNSAFE". See the header for details on this. - if (model_type_ == SLiMModelType::kModelTypeWF) + if (f_single_trait) { - individual_cached_fitness_OVERRIDE_ = true; - individual_cached_fitness_OVERRIDE_value_ = fitness; + fitness *= trait_info[single_trait_index].phenotype_; } else { - EIDOS_BENCHMARK_START(EidosBenchmarkType::k_FITNESS_SEX_2); - EIDOS_THREAD_COUNT(gEidos_OMP_threads_FITNESS_SEX_2); -#pragma omp parallel for schedule(static) default(none) shared(parent_subpop_size_) firstprivate(fitness) if(parent_subpop_size_ >= EIDOS_OMPMIN_FITNESS_SEX_2) num_threads(thread_count) - for (slim_popsize_t female_index = 0; female_index < parent_first_male_index_; female_index++) - { - parent_individuals_[female_index]->cached_fitness_UNSAFE_ = fitness; - } - EIDOS_BENCHMARK_END(EidosBenchmarkType::k_FITNESS_SEX_2); - } - - totalFemaleFitness = fitness * parent_first_male_index_; - } - } - else if (skip_chromosomal_fitness) - { - if (!needs_shuffle) - { - for (slim_popsize_t female_index = 0; female_index < parent_first_male_index_; female_index++) - { - double fitness = parent_individuals_[female_index]->fitness_scaling_; - - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, female_index); - -#ifdef SLIMGUI - parent_individuals_[female_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - parent_individuals_[female_index]->cached_fitness_UNSAFE_ = fitness; - totalFemaleFitness += fitness; + for (int64_t trait_index : p_direct_effect_trait_indices) + fitness *= trait_info[trait_index].phenotype_; // >= 0.0 for multiplicative traits } } - else + + if (!f_has_trait_effects || (fitness > (slim_fitness_t)0.0)) { - // general case for females without chromosomal fitness; shuffle buffer needed - slim_popsize_t *shuffle_buf = species_.BorrowShuffleBuffer(parent_first_male_index_); - - for (slim_popsize_t shuffle_index = 0; shuffle_index < parent_first_male_index_; shuffle_index++) - { - slim_popsize_t female_index = shuffle_buf[shuffle_index]; - double fitness = parent_individuals_[female_index]->fitness_scaling_; - - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, female_index); - -#ifdef SLIMGUI - parent_individuals_[female_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - parent_individuals_[female_index]->cached_fitness_UNSAFE_ = fitness; - totalFemaleFitness += fitness; - } + // fitness is > 0.0, so continue calculating - species_.ReturnShuffleBuffer(); - } - } - else - { - if (!needs_shuffle) - { - // FIXME should have some additional criterion for whether to go parallel with this, like the number of mutations - if (!mutationEffect_callbacks_exist && !fitnessEffect_callbacks_exist) - { - // a separate loop for parallelization of the no-callback case - -#if (defined(_OPENMP) && SLIM_USE_NONNEUTRAL_CACHES) - // we need to fix the nonneutral caches in a separate pass first - // because all the correct caches need to get flushed to everyone - // before beginning fitness evaluation, for efficiency - // beginend_nonneutral_pointers() handles the non-parallel case - FixNonNeutralCaches_OMP(); -#endif - - EIDOS_BENCHMARK_START(EidosBenchmarkType::k_FITNESS_SEX_3); - EIDOS_THREAD_COUNT(gEidos_OMP_threads_FITNESS_SEX_3); -#pragma omp parallel for schedule(dynamic, 16) default(none) shared(parent_first_male_index_, subpop_fitness_scaling) reduction(+: totalFemaleFitness) if(parent_subpop_size_ >= EIDOS_OMPMIN_FITNESS_SEX_3) num_threads(thread_count) - for (slim_popsize_t female_index = 0; female_index < parent_first_male_index_; female_index++) - { - double fitness = parent_individuals_[female_index]->fitness_scaling_; - - if (fitness > 0.0) - { - fitness *= (this->*FitnessOfParent_TEMPLATED)(female_index, p_mutationEffect_callbacks); - -#ifdef SLIMGUI - parent_individuals_[female_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - } - else - { -#ifdef SLIMGUI - parent_individuals_[female_index]->cached_unscaled_fitness_ = fitness; -#endif - } - - parent_individuals_[female_index]->cached_fitness_UNSAFE_ = fitness; - totalFemaleFitness += fitness; - } - EIDOS_BENCHMARK_END(EidosBenchmarkType::k_FITNESS_SEX_3); - } - else // at least one mutationEffect() or fitnessEffect() callback; not parallelized - { - for (slim_popsize_t female_index = 0; female_index < parent_first_male_index_; female_index++) - { - double fitness = parent_individuals_[female_index]->fitness_scaling_; - - if (fitness > 0.0) - { - fitness *= (this->*FitnessOfParent_TEMPLATED)(female_index, p_mutationEffect_callbacks); - - // multiply in the effects of any fitnessEffect() callbacks - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, female_index); - -#ifdef SLIMGUI - parent_individuals_[female_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - } - else - { -#ifdef SLIMGUI - parent_individuals_[female_index]->cached_unscaled_fitness_ = fitness; -#endif - } - - parent_individuals_[female_index]->cached_fitness_UNSAFE_ = fitness; - totalFemaleFitness += fitness; - } - } - } - else - { - // general case for females; we use the shuffle buffer to randomize processing order - slim_popsize_t *shuffle_buf = species_.BorrowShuffleBuffer(parent_first_male_index_); + if (f_has_fitnessEffect_callbacks) + fitness *= (slim_fitness_t)ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, ind); // guaranteed >= 0.0 - for (slim_popsize_t shuffle_index = 0; shuffle_index < parent_first_male_index_; shuffle_index++) - { - slim_popsize_t female_index = shuffle_buf[shuffle_index]; - double fitness = parent_individuals_[female_index]->fitness_scaling_; - - if (fitness > 0.0) - { - fitness *= (this->*FitnessOfParent_TEMPLATED)(female_index, p_mutationEffect_callbacks); - - // multiply in the effects of any fitnessEffect() callbacks - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, female_index); - #ifdef SLIMGUI - parent_individuals_[female_index]->cached_unscaled_fitness_ = fitness; + ind->cached_unscaled_fitness_ = fitness; #endif - - fitness *= subpop_fitness_scaling; - } - else - { -#ifdef SLIMGUI - parent_individuals_[female_index]->cached_unscaled_fitness_ = fitness; -#endif - } - - parent_individuals_[female_index]->cached_fitness_UNSAFE_ = fitness; - totalFemaleFitness += fitness; - } - species_.ReturnShuffleBuffer(); - } - } - - totalFitness += totalFemaleFitness; - if ((model_type_ == SLiMModelType::kModelTypeWF) && (totalFemaleFitness <= 0.0)) - EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of females is <= 0.0." << EidosTerminate(nullptr); - - // Set up to draw random males - if (pure_neutral) - { - if (Individual::s_any_individual_fitness_scaling_set_) - { - EIDOS_BENCHMARK_START(EidosBenchmarkType::k_FITNESS_SEX_1); - EIDOS_THREAD_COUNT(gEidos_OMP_threads_FITNESS_SEX_1); -#pragma omp parallel for schedule(static) default(none) shared(parent_subpop_size_) firstprivate(subpop_fitness_scaling) reduction(+: totalMaleFitness) if(parent_subpop_size_ >= EIDOS_OMPMIN_FITNESS_SEX_1) num_threads(thread_count) - for (slim_popsize_t male_index = parent_first_male_index_; male_index < parent_subpop_size_; male_index++) - { - double fitness = parent_individuals_[male_index]->fitness_scaling_; - -#ifdef SLIMGUI - parent_individuals_[male_index]->cached_unscaled_fitness_ = fitness; -#endif - + if (f_has_subpop_fitnessScaling) fitness *= subpop_fitness_scaling; - parent_individuals_[male_index]->cached_fitness_UNSAFE_ = fitness; - totalMaleFitness += fitness; - } - EIDOS_BENCHMARK_END(EidosBenchmarkType::k_FITNESS_SEX_1); - } - else - { -#ifdef SLIMGUI - for (slim_popsize_t male_index = parent_first_male_index_; male_index < parent_subpop_size_; male_index++) - parent_individuals_[male_index]->cached_unscaled_fitness_ = 1.0; -#endif - double fitness = subpop_fitness_scaling; // no individual fitness_scaling_ - - // Here we override setting up every cached_fitness_UNSAFE_ value, and set up a subpop-level cache instead. - // This is why cached_fitness_UNSAFE_ is marked "UNSAFE". See the header for details on this. - if (model_type_ == SLiMModelType::kModelTypeWF) - { - individual_cached_fitness_OVERRIDE_ = true; - individual_cached_fitness_OVERRIDE_value_ = fitness; - } - else - { - EIDOS_BENCHMARK_START(EidosBenchmarkType::k_FITNESS_SEX_2); - EIDOS_THREAD_COUNT(gEidos_OMP_threads_FITNESS_SEX_2); -#pragma omp parallel for schedule(static) default(none) shared(parent_subpop_size_) firstprivate(fitness) if(parent_subpop_size_ >= EIDOS_OMPMIN_FITNESS_SEX_2) num_threads(thread_count) - for (slim_popsize_t male_index = parent_first_male_index_; male_index < parent_subpop_size_; male_index++) - { - parent_individuals_[male_index]->cached_fitness_UNSAFE_ = fitness; - } - EIDOS_BENCHMARK_END(EidosBenchmarkType::k_FITNESS_SEX_2); - } - - if (parent_subpop_size_ > parent_first_male_index_) - totalMaleFitness = fitness * (parent_subpop_size_ - parent_first_male_index_); - } - } - else if (skip_chromosomal_fitness) - { - if (!needs_shuffle) - { - for (slim_popsize_t male_index = parent_first_male_index_; male_index < parent_subpop_size_; male_index++) - { - double fitness = parent_individuals_[male_index]->fitness_scaling_; - - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, male_index); - -#ifdef SLIMGUI - parent_individuals_[male_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - parent_individuals_[male_index]->cached_fitness_UNSAFE_ = fitness; - totalMaleFitness += fitness; - } + ind->cached_fitness_UNSAFE_ = fitness; } else { - // general case for females without chromosomal fitness; shuffle buffer needed - slim_popsize_t male_count = parent_subpop_size_ - parent_first_male_index_; - slim_popsize_t *shuffle_buf = species_.BorrowShuffleBuffer(male_count); - - for (slim_popsize_t shuffle_index = 0; shuffle_index < male_count; shuffle_index++) - { - slim_popsize_t male_index = parent_first_male_index_ + shuffle_buf[shuffle_index]; - double fitness = parent_individuals_[male_index]->fitness_scaling_; - - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, male_index); - + // with additive traits, fitness could be < 0.0 (and gets clipped to 0.0); otherwise it is 0.0 + // we're already fitness 0.0, so we can skip multiplying in subpop_fitness_scaling #ifdef SLIMGUI - parent_individuals_[male_index]->cached_unscaled_fitness_ = fitness; + ind->cached_unscaled_fitness_ = 0.0; #endif - - fitness *= subpop_fitness_scaling; - parent_individuals_[male_index]->cached_fitness_UNSAFE_ = fitness; - totalMaleFitness += fitness; - } - - species_.ReturnShuffleBuffer(); + ind->cached_fitness_UNSAFE_ = 0.0; } } else { - if (!needs_shuffle) - { - // FIXME should have some additional criterion for whether to go parallel with this, like the number of mutations - if (!mutationEffect_callbacks_exist && !fitnessEffect_callbacks_exist) - { - // a separate loop for parallelization of the no-callback case - // note that we rely on the fixup of non-neutral caches done above - EIDOS_BENCHMARK_START(EidosBenchmarkType::k_FITNESS_SEX_3); - EIDOS_THREAD_COUNT(gEidos_OMP_threads_FITNESS_SEX_3); -#pragma omp parallel for schedule(dynamic, 16) default(none) shared(parent_first_male_index_, parent_subpop_size_, subpop_fitness_scaling) reduction(+: totalMaleFitness) if(parent_subpop_size_ >= EIDOS_OMPMIN_FITNESS_SEX_3) num_threads(thread_count) - for (slim_popsize_t male_index = parent_first_male_index_; male_index < parent_subpop_size_; male_index++) - { - double fitness = parent_individuals_[male_index]->fitness_scaling_; - - if (fitness > 0.0) - { - fitness *= (this->*FitnessOfParent_TEMPLATED)(male_index, p_mutationEffect_callbacks); - -#ifdef SLIMGUI - parent_individuals_[male_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - } - else - { -#ifdef SLIMGUI - parent_individuals_[male_index]->cached_unscaled_fitness_ = fitness; -#endif - } - - parent_individuals_[male_index]->cached_fitness_UNSAFE_ = fitness; - totalMaleFitness += fitness; - } - EIDOS_BENCHMARK_END(EidosBenchmarkType::k_FITNESS_SEX_3); - } - else // at least one mutationEffect() or fitnessEffect() callback; not parallelized - { - for (slim_popsize_t male_index = parent_first_male_index_; male_index < parent_subpop_size_; male_index++) - { - double fitness = parent_individuals_[male_index]->fitness_scaling_; - - if (fitness > 0.0) - { - fitness *= (this->*FitnessOfParent_TEMPLATED)(male_index, p_mutationEffect_callbacks); - - // multiply in the effects of any fitnessEffect() callbacks - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, male_index); - -#ifdef SLIMGUI - parent_individuals_[male_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - } - else - { -#ifdef SLIMGUI - parent_individuals_[male_index]->cached_unscaled_fitness_ = fitness; -#endif - } - - parent_individuals_[male_index]->cached_fitness_UNSAFE_ = fitness; - totalMaleFitness += fitness; - } - } - } - else - { - // general case for males; we use the shuffle buffer to randomize processing order - slim_popsize_t male_count = parent_subpop_size_ - parent_first_male_index_; - slim_popsize_t *shuffle_buf = species_.BorrowShuffleBuffer(male_count); - - for (slim_popsize_t shuffle_index = 0; shuffle_index < male_count; shuffle_index++) - { - slim_popsize_t male_index = parent_first_male_index_ + shuffle_buf[shuffle_index]; - double fitness = parent_individuals_[male_index]->fitness_scaling_; - - if (fitness > 0.0) - { - fitness *= (this->*FitnessOfParent_TEMPLATED)(male_index, p_mutationEffect_callbacks); - - // multiply in the effects of any fitnessEffect() callbacks - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, male_index); - -#ifdef SLIMGUI - parent_individuals_[male_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - } - else - { + // fitness is 0.0; we refer to it (in-register, presumably) rather than use 0.0 #ifdef SLIMGUI - parent_individuals_[male_index]->cached_unscaled_fitness_ = fitness; + ind->cached_unscaled_fitness_ = fitness; #endif - } - - parent_individuals_[male_index]->cached_fitness_UNSAFE_ = fitness; - totalMaleFitness += fitness; - } - - species_.ReturnShuffleBuffer(); - } + ind->cached_fitness_UNSAFE_ = fitness; } + } + + if (f_has_shuffle_buffer) + species_.ReturnShuffleBuffer(); +} + +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); + +// WF only: +void Subpopulation::UpdateWFFitnessBuffers(void) +{ + // This is called only by UpdateFitness(), after the fitness of all individuals has been updated, and only in + // WF models. It updates cached fitness buffers, and then generates GSL-based lookup tables for mate choice. + + if (individual_cached_fitness_OVERRIDE_) + { + // This is the optimized case, where all individuals have the same fitness and it is cached at the subpop + // level. When that is the case, we don't use the GSL discrete preproc stuff to choose mates proportional + // to fitness; we choose mates randomly with equal probability instead. Given that, we don't need to set + // up the buffers (cached_parental_fitness_, etc.) either; they are only used to set up the GSL's discrete + // preproc machinery. So we can actually free those buffers to decrease memory footprint, in this path. + if (cached_parental_fitness_) + free(cached_parental_fitness_); - totalFitness += totalMaleFitness; + if (cached_male_fitness_) + free(cached_male_fitness_); - if (model_type_ == SLiMModelType::kModelTypeWF) + cached_fitness_size_ = 0; + cached_fitness_capacity_ = 0; + + // Then we just do a trivial check for numerical problems. + double universal_cached_fitness = individual_cached_fitness_OVERRIDE_value_; + + if (sex_enabled_) { + double totalMaleFitness = universal_cached_fitness * (parent_subpop_size_ - parent_first_male_index_); + double totalFemaleFitness = universal_cached_fitness * parent_first_male_index_; + if (totalMaleFitness <= 0.0) EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of males is <= 0.0." << EidosTerminate(nullptr); - if (!std::isfinite(totalFitness)) + if (totalFemaleFitness <= 0.0) + EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of females is <= 0.0." << EidosTerminate(nullptr); + + if (!std::isfinite(totalMaleFitness + totalFemaleFitness)) EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of subpopulation is not finite; numerical error will prevent accurate simulation." << EidosTerminate(nullptr); } - } - else - { - if (pure_neutral) - { - if (Individual::s_any_individual_fitness_scaling_set_) - { - EIDOS_BENCHMARK_START(EidosBenchmarkType::k_FITNESS_ASEX_1); - EIDOS_THREAD_COUNT(gEidos_OMP_threads_FITNESS_ASEX_1); -#pragma omp parallel for schedule(static) default(none) shared(parent_subpop_size_) firstprivate(subpop_fitness_scaling) reduction(+: totalFitness) if(parent_subpop_size_ >= EIDOS_OMPMIN_FITNESS_ASEX_1) num_threads(thread_count) - for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) - { - double fitness = parent_individuals_[individual_index]->fitness_scaling_; - -#ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - parent_individuals_[individual_index]->cached_fitness_UNSAFE_ = fitness; - totalFitness += fitness; - } - EIDOS_BENCHMARK_END(EidosBenchmarkType::k_FITNESS_ASEX_1); - } - else - { -#ifdef SLIMGUI - for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) - parent_individuals_[individual_index]->cached_unscaled_fitness_ = 1.0; -#endif - - double fitness = subpop_fitness_scaling; // no individual fitness_scaling_ - - // Here we override setting up every cached_fitness_UNSAFE_ value, and set up a subpop-level cache instead. - // This is why cached_fitness_UNSAFE_ is marked "UNSAFE". See the header for details on this. - if (model_type_ == SLiMModelType::kModelTypeWF) - { - individual_cached_fitness_OVERRIDE_ = true; - individual_cached_fitness_OVERRIDE_value_ = fitness; - } - else - { - EIDOS_BENCHMARK_START(EidosBenchmarkType::k_FITNESS_ASEX_2); - EIDOS_THREAD_COUNT(gEidos_OMP_threads_FITNESS_ASEX_2); -#pragma omp parallel for schedule(static) default(none) shared(parent_subpop_size_) firstprivate(fitness) if(parent_subpop_size_ >= EIDOS_OMPMIN_FITNESS_ASEX_2) num_threads(thread_count) - for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) - { - parent_individuals_[individual_index]->cached_fitness_UNSAFE_ = fitness; - } - EIDOS_BENCHMARK_END(EidosBenchmarkType::k_FITNESS_ASEX_2); - } - - totalFitness = fitness * parent_subpop_size_; - } - } - else if (skip_chromosomal_fitness) - { - if (!needs_shuffle) - { - for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) - { - double fitness = parent_individuals_[individual_index]->fitness_scaling_; - - // multiply in the effects of any fitnessEffect() callbacks - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, individual_index); - -#ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - parent_individuals_[individual_index]->cached_fitness_UNSAFE_ = fitness; - totalFitness += fitness; - } - } - else - { - // general case for hermaphrodites without chromosomal fitness; shuffle buffer needed - slim_popsize_t *shuffle_buf = species_.BorrowShuffleBuffer(parent_subpop_size_); - - for (slim_popsize_t shuffle_index = 0; shuffle_index < parent_subpop_size_; shuffle_index++) - { - slim_popsize_t individual_index = shuffle_buf[shuffle_index]; - double fitness = parent_individuals_[individual_index]->fitness_scaling_; - - // multiply in the effects of any fitnessEffect() callbacks - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, individual_index); - -#ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - parent_individuals_[individual_index]->cached_fitness_UNSAFE_ = fitness; - totalFitness += fitness; - } - - species_.ReturnShuffleBuffer(); - } - } else { - if (!needs_shuffle) - { - // FIXME should have some additional criterion for whether to go parallel with this, like the number of mutations - if (!mutationEffect_callbacks_exist && !fitnessEffect_callbacks_exist) - { - // a separate loop for parallelization of the no-callback case - -#if (defined(_OPENMP) && SLIM_USE_NONNEUTRAL_CACHES) - // we need to fix the nonneutral caches in a separate pass first - // because all the correct caches need to get flushed to everyone - // before beginning fitness evaluation, for efficiency - // beginend_nonneutral_pointers() handles the non-parallel case - FixNonNeutralCaches_OMP(); -#endif - - EIDOS_BENCHMARK_START(EidosBenchmarkType::k_FITNESS_ASEX_3); - EIDOS_THREAD_COUNT(gEidos_OMP_threads_FITNESS_ASEX_3); -#pragma omp parallel for schedule(dynamic, 16) default(none) shared(parent_subpop_size_, subpop_fitness_scaling) reduction(+: totalFitness) if(parent_subpop_size_ >= EIDOS_OMPMIN_FITNESS_ASEX_3) num_threads(thread_count) - for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) - { - double fitness = parent_individuals_[individual_index]->fitness_scaling_; - - if (fitness > 0.0) - { - fitness *= (this->*FitnessOfParent_TEMPLATED)(individual_index, p_mutationEffect_callbacks); - -#ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - } - else - { -#ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; -#endif - } - - parent_individuals_[individual_index]->cached_fitness_UNSAFE_ = fitness; - totalFitness += fitness; - } - EIDOS_BENCHMARK_END(EidosBenchmarkType::k_FITNESS_ASEX_3); - } - else // at least one mutationEffect() or fitnessEffect() callback; not parallelized - { - for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) - { - double fitness = parent_individuals_[individual_index]->fitness_scaling_; - - if (fitness > 0.0) - { - fitness *= (this->*FitnessOfParent_TEMPLATED)(individual_index, p_mutationEffect_callbacks); - - // multiply in the effects of any fitnessEffect() callbacks - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, individual_index); - -#ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - } - else - { -#ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; -#endif - } - - parent_individuals_[individual_index]->cached_fitness_UNSAFE_ = fitness; - totalFitness += fitness; - } - } - } - else - { - // general case for hermaphrodites; we use the shuffle buffer to randomize processing order - slim_popsize_t *shuffle_buf = species_.BorrowShuffleBuffer(parent_subpop_size_); - - for (slim_popsize_t shuffle_index = 0; shuffle_index < parent_subpop_size_; shuffle_index++) - { - slim_popsize_t individual_index = shuffle_buf[shuffle_index]; - double fitness = parent_individuals_[individual_index]->fitness_scaling_; - - if (fitness > 0.0) - { - fitness *= (this->*FitnessOfParent_TEMPLATED)(individual_index, p_mutationEffect_callbacks); - - // multiply in the effects of any fitnessEffect() callbacks - if (fitnessEffect_callbacks_exist && (fitness > 0.0)) - fitness *= ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, individual_index); - -#ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; -#endif - - fitness *= subpop_fitness_scaling; - } - else - { -#ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; -#endif - } - - parent_individuals_[individual_index]->cached_fitness_UNSAFE_ = fitness; - totalFitness += fitness; - } - - species_.ReturnShuffleBuffer(); - } - } - - if (model_type_ == SLiMModelType::kModelTypeWF) - { + double totalFitness = universal_cached_fitness * parent_subpop_size_; + if (totalFitness <= 0.0) EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of all individuals is <= 0.0." << EidosTerminate(nullptr); if (!std::isfinite(totalFitness)) EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of subpopulation is not finite; numerical error will prevent accurate simulation." << EidosTerminate(nullptr); } } - - // Mutrun experiment timing can be per-individual, per-chromosome, but that entails a lot of timing overhead. - // To avoid that overhead, in single-chromosome models we just time across the whole round of fitness evals - // instead. Note that in this case we chose a template above for FitnessOfParent() that does not time. - if (species_.DoingAnyMutationRunExperiments() && (species_.Chromosomes().size() == 1)) - species_.Chromosomes()[0]->StopMutationRunExperimentClock("UpdateFitness()"); - - if (model_type_ == SLiMModelType::kModelTypeWF) - UpdateWFFitnessBuffers(pure_neutral && !Individual::s_any_individual_fitness_scaling_set_); -} - -// WF only: -void Subpopulation::UpdateWFFitnessBuffers(bool p_pure_neutral) -{ - // This is called only by UpdateFitness(), after the fitness of all individuals has been updated, and only in WF models. - // It updates the cached_parental_fitness_ and cached_male_fitness_ buffers, and then generates new lookup tables for mate choice. - - // Reallocate the fitness buffers to be large enough - if (cached_fitness_capacity_ < parent_subpop_size_) + else { - cached_parental_fitness_ = (double *)realloc(cached_parental_fitness_, sizeof(double) * parent_subpop_size_); - if (!cached_parental_fitness_) - EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateWFFitnessBuffers): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + // This is the normal case, where cached_fitness_UNSAFE_ has cached fitness values for each individual. + // In this case we need to set up buffers to create the GSL discrete preproc structs for drawing parents. + // In this code path we also need to total up individual fitness values to check for numerical problems. - if (sex_enabled_) + // Reallocate the fitness buffers to be large enough; note that we up-cast to double here for the GSL. + // Due to the shenanigans we pull in ApplyMateChoiceCallbacks(), we might have a non-zero capacity set + // and yet have an unallocated buffer (especially in the sex case, at least for now); so we check that. + if (!cached_parental_fitness_ || (sex_enabled_ && !cached_male_fitness_) || (cached_fitness_capacity_ < parent_subpop_size_)) { - cached_male_fitness_ = (double *)realloc(cached_male_fitness_, sizeof(double) * parent_subpop_size_); - if (!cached_male_fitness_) + // there might be an existing capacity but a missing buffer; use the existing value if it's bigger + cached_fitness_capacity_ = std::max(cached_fitness_capacity_, parent_subpop_size_); + + cached_parental_fitness_ = (double *)realloc(cached_parental_fitness_, sizeof(double) * parent_subpop_size_); + if (!cached_parental_fitness_) EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateWFFitnessBuffers): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - } - - cached_fitness_capacity_ = parent_subpop_size_; - } - - // Set up the fitness buffers with the new information - if (individual_cached_fitness_OVERRIDE_) - { - // This is the optimized case, where all individuals have the same fitness and it is cached at the subpop level - double universal_cached_fitness = individual_cached_fitness_OVERRIDE_value_; - - if (sex_enabled_) - { - for (slim_popsize_t female_index = 0; female_index < parent_first_male_index_; female_index++) - { - cached_parental_fitness_[female_index] = universal_cached_fitness; - cached_male_fitness_[female_index] = 0; - } - for (slim_popsize_t male_index = parent_first_male_index_; male_index < parent_subpop_size_; male_index++) - { - cached_parental_fitness_[male_index] = universal_cached_fitness; - cached_male_fitness_[male_index] = universal_cached_fitness; - } - } - else - { - for (slim_popsize_t i = 0; i < parent_subpop_size_; i++) - cached_parental_fitness_[i] = universal_cached_fitness; + if (sex_enabled_) + { + cached_male_fitness_ = (double *)realloc(cached_male_fitness_, sizeof(double) * parent_subpop_size_); + if (!cached_male_fitness_) + EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateWFFitnessBuffers): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + } } - } - else - { - // This is the normal case, where cached_fitness_UNSAFE_ has the cached fitness values for each individual + if (sex_enabled_) { + double totalMaleFitness = 0.0, totalFemaleFitness = 0.0; + for (slim_popsize_t female_index = 0; female_index < parent_first_male_index_; female_index++) { - double fitness = parent_individuals_[female_index]->cached_fitness_UNSAFE_; + double fitness = (double)parent_individuals_[female_index]->cached_fitness_UNSAFE_; cached_parental_fitness_[female_index] = fitness; cached_male_fitness_[female_index] = 0; + totalFemaleFitness += fitness; } for (slim_popsize_t male_index = parent_first_male_index_; male_index < parent_subpop_size_; male_index++) { - double fitness = parent_individuals_[male_index]->cached_fitness_UNSAFE_; + double fitness = (double)parent_individuals_[male_index]->cached_fitness_UNSAFE_; cached_parental_fitness_[male_index] = fitness; cached_male_fitness_[male_index] = fitness; + totalMaleFitness += fitness; } + + if (totalMaleFitness <= 0.0) + EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of males is <= 0.0." << EidosTerminate(nullptr); + if (totalFemaleFitness <= 0.0) + EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of females is <= 0.0." << EidosTerminate(nullptr); + + if (!std::isfinite(totalMaleFitness + totalFemaleFitness)) + EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of subpopulation is not finite; numerical error will prevent accurate simulation." << EidosTerminate(nullptr); } else { + double totalFitness = 0.0; + for (slim_popsize_t i = 0; i < parent_subpop_size_; i++) - cached_parental_fitness_[i] = parent_individuals_[i]->cached_fitness_UNSAFE_; + { + double fitness = (double)parent_individuals_[i]->cached_fitness_UNSAFE_; + + cached_parental_fitness_[i] = fitness; + totalFitness += fitness; + } + + if (totalFitness <= 0.0) + EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of all individuals is <= 0.0." << EidosTerminate(nullptr); + if (!std::isfinite(totalFitness)) + EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of subpopulation is not finite; numerical error will prevent accurate simulation." << EidosTerminate(nullptr); } + + cached_fitness_size_ = parent_subpop_size_; } - cached_fitness_size_ = parent_subpop_size_; - // Remake our mate-choice lookup tables if (sex_enabled_) { @@ -2423,8 +1742,8 @@ void Subpopulation::UpdateWFFitnessBuffers(bool p_pure_neutral) lookup_male_parent_ = nullptr; } - // in pure neutral models we don't set up the discrete preproc - if (!p_pure_neutral) + // we don't set up the discrete preproc if all individuals have equal fitness + if (!individual_cached_fitness_OVERRIDE_) { lookup_female_parent_ = gsl_ran_discrete_preproc(parent_first_male_index_, cached_parental_fitness_); lookup_male_parent_ = gsl_ran_discrete_preproc(parent_subpop_size_ - parent_first_male_index_, cached_parental_fitness_ + parent_first_male_index_); @@ -2439,8 +1758,8 @@ void Subpopulation::UpdateWFFitnessBuffers(bool p_pure_neutral) lookup_parent_ = nullptr; } - // in pure neutral models we don't set up the discrete preproc - if (!p_pure_neutral) + // we don't set up the discrete preproc if all individuals have equal fitness + if (!individual_cached_fitness_OVERRIDE_) { lookup_parent_ = gsl_ran_discrete_preproc(parent_subpop_size_, cached_parental_fitness_); } @@ -2448,7 +1767,7 @@ void Subpopulation::UpdateWFFitnessBuffers(bool p_pure_neutral) } // FIXME MULTITRAIT: should return slim_effect_t so the caller doesn't have to cast -double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, double p_computed_fitness, std::vector &p_mutationEffect_callbacks, Individual *p_individual) +slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::ApplyMutationEffectCallbacks(): running Eidos callback"); @@ -2511,10 +1830,10 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int #if DEBUG // this checks the value type at runtime - p_computed_fitness = result->FloatData()[0]; + p_effect = (slim_effect_t)result->FloatData()[0]; #else // unsafe cast for speed - p_computed_fitness = ((EidosValue_Float *)result)->data()[0]; + p_effect = (slim_effect_t)((EidosValue_Float *)result)->data()[0]; #endif // the cached value is owned by the tree, so we do not dispose of it @@ -2531,7 +1850,7 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int { double A = mutationEffect_callback->cached_opt_A_; - p_computed_fitness = (A / p_computed_fitness); // p_computed_fitness is effect + p_effect = (slim_effect_t)(A / (double)p_effect); } else { @@ -2542,7 +1861,7 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int { // local variables for the callback parameters that we might need to allocate here, and thus need to free below EidosValue_Object local_mut(mut_block_ptr + p_mutation, gSLiM_Mutation_Class); - EidosValue_Float local_effect(p_computed_fitness); + EidosValue_Float local_effect((double)p_effect); // We need to actually execute the script; we start a block here to manage the lifetime of the symbol table { @@ -2600,10 +1919,10 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int #if DEBUG // this checks the value type at runtime - p_computed_fitness = result->FloatData()[0]; + p_effect = (slim_effect_t)result->FloatData()[0]; #else // unsafe cast for speed - p_computed_fitness = ((EidosValue_Float *)result)->data()[0]; + p_effect = (slim_effect_t)((EidosValue_Float *)result)->data()[0]; #endif } catch (...) @@ -2622,10 +1941,10 @@ double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int SLIM_PROFILE_BLOCK_END(community_.profile_callback_totals_[(int)(SLiMEidosBlockType::SLiMEidosMutationEffectCallback)]); #endif - return p_computed_fitness; + return p_effect; } -double Subpopulation::ApplyFitnessEffectCallbacks(std::vector &p_fitnessEffect_callbacks, slim_popsize_t p_individual_index) +slim_fitness_t Subpopulation::ApplyFitnessEffectCallbacks(std::vector &p_fitnessEffect_callbacks, Individual *p_individual) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::ApplyFitnessEffectCallbacks(): running Eidos callback"); @@ -2634,8 +1953,7 @@ double Subpopulation::ApplyFitnessEffectCallbacks(std::vector & SLIM_PROFILE_BLOCK_START(); #endif - double computed_fitness = 1.0; - Individual *individual = parent_individuals_[p_individual_index]; + slim_fitness_t computed_fitness = 1.0; for (SLiMEidosBlock *fitnessEffect_callback : p_fitnessEffect_callbacks) { @@ -2681,10 +1999,10 @@ double Subpopulation::ApplyFitnessEffectCallbacks(std::vector & #if DEBUG // this checks the value type at runtime - computed_fitness *= result->FloatData()[0]; + computed_fitness *= (slim_fitness_t)result->FloatData()[0]; #else // unsafe cast for speed - computed_fitness *= ((EidosValue_Float *)result)->data()[0]; + computed_fitness *= (slim_fitness_t)((EidosValue_Float *)result)->data()[0]; #endif // the cached value is owned by the tree, so we do not dispose of it @@ -2704,7 +2022,7 @@ double Subpopulation::ApplyFitnessEffectCallbacks(std::vector & double C = fitnessEffect_callback->cached_opt_C_; double D = fitnessEffect_callback->cached_opt_D_; - computed_fitness *= (D + (gsl_ran_gaussian_pdf(individual->tagF_value_ - A, B) / C)); + computed_fitness *= (slim_fitness_t)(D + (gsl_ran_gaussian_pdf(p_individual->tagF_value_ - A, B) / C)); } else { @@ -2733,7 +2051,7 @@ double Subpopulation::ApplyFitnessEffectCallbacks(std::vector & // referred to by the values may change, but the values themselves will not change). // BCH 11/7/2025: note these symbols are now protected in SLiM_ConfigureContext() if (fitnessEffect_callback->contains_individual_) - callback_symbols.InitializeConstantSymbolEntry(gID_individual, individual->CachedEidosValue()); + callback_symbols.InitializeConstantSymbolEntry(gID_individual, p_individual->CachedEidosValue()); if (fitnessEffect_callback->contains_subpop_) callback_symbols.InitializeConstantSymbolEntry(gID_subpop, SymbolTableEntry().second); @@ -2748,10 +2066,10 @@ double Subpopulation::ApplyFitnessEffectCallbacks(std::vector & #if DEBUG // this checks the value type at runtime - computed_fitness *= result->FloatData()[0]; + computed_fitness *= (slim_fitness_t)result->FloatData()[0]; #else // unsafe cast for speed - computed_fitness *= ((EidosValue_Float *)result)->data()[0]; + computed_fitness *= (slim_fitness_t)((EidosValue_Float *)result)->data()[0]; #endif } catch (...) @@ -2777,7 +2095,7 @@ double Subpopulation::ApplyFitnessEffectCallbacks(std::vector & } // If any callback puts us at or below zero, we can short-circuit the rest - if (computed_fitness <= 0.0) + if (computed_fitness <= (slim_fitness_t)0.0) { computed_fitness = 0.0; break; @@ -2793,570 +2111,6 @@ double Subpopulation::ApplyFitnessEffectCallbacks(std::vector & return computed_fitness; } -// FitnessOfParent() has three templated versions, for no callbacks, a single callback, and multiple callbacks. That -// pattern extends downward to _Fitness_DiploidChromosome() and _Fitness_HaploidChromosome(), which is the level where -// it actually matters; in FitnessOfParent() the template flags just get passed through. The goal of this design is -// twofold. First, it allows the case without mutationEffect() callbacks to run at full speed. Second, it allows the -// single-callback case to be optimized in a special way. When there is just a single callback, it usually refers to -// a mutation type that is relatively uncommon. The model might have neutral mutations in most cases, plus a rare -// (or unique) mutation type that is subject to more complex selection, for example. We can optimize that very common -// case substantially by making the callout to ApplyMutationEffectCallbacks() only for mutations of the mutation type -// that the callback modifies. This pays off mostly when there are many common mutations with no callback, plus one -// rare mutation type that has a callback. A model of neutral drift across a long chromosome with a high mutation -// rate, with an introduced beneficial mutation with a selection coefficient extremely close to 0, for example, would -// hit this case hard and see a speedup of as much as 25%, so the additional complexity seems worth it (since that's -// quite a realistic and common case). The only unfortunate thing about this design is that p_mutationEffect_callbacks -// has to get passed all the way down, even when we know we won't use it. LTO might optimize that away, with luck. -template -double Subpopulation::FitnessOfParent(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks) -{ - // calculate the fitness of the individual at index p_individual_index in the parent population - // this loops through all chromosomes, handling ploidy and callbacks as needed - double w = 1.0; - Individual *individual = parent_individuals_[p_individual_index]; - int haplosome_index = 0; - - for (Chromosome *chromosome : species_.Chromosomes()) - { - if (f_mutrunexps) chromosome->StartMutationRunExperimentClock(); - - switch (chromosome->Type()) - { - // diploid, possibly with one or both being null haplosomes - case ChromosomeType::kA_DiploidAutosome: - case ChromosomeType::kX_XSexChromosome: - case ChromosomeType::kZ_ZSexChromosome: - { - Haplosome *haplosome1 = individual->haplosomes_[haplosome_index]; - Haplosome *haplosome2 = individual->haplosomes_[haplosome_index+1]; - - w *= _Fitness_DiploidChromosome(haplosome1, haplosome2, p_mutationEffect_callbacks); - if (w <= 0.0) { - if (f_mutrunexps) chromosome->StopMutationRunExperimentClock("FitnessOfParent()"); - return 0.0; - } - - haplosome_index += 2; - break; - } - - // haploid, possibly null - case ChromosomeType::kH_HaploidAutosome: - case ChromosomeType::kY_YSexChromosome: - case ChromosomeType::kW_WSexChromosome: - case ChromosomeType::kHF_HaploidFemaleInherited: - case ChromosomeType::kFL_HaploidFemaleLine: - case ChromosomeType::kHM_HaploidMaleInherited: - case ChromosomeType::kML_HaploidMaleLine: - { - Haplosome *haplosome = individual->haplosomes_[haplosome_index]; - - w *= _Fitness_HaploidChromosome(haplosome, p_mutationEffect_callbacks); - if (w <= 0.0) { - if (f_mutrunexps) chromosome->StopMutationRunExperimentClock("FitnessOfParent()"); - return 0.0; - } - - haplosome_index += 1; - break; - } - - // special cases: haploid but with an accompanying null - case ChromosomeType::kHNull_HaploidAutosomeWithNull: - { - Haplosome *haplosome = individual->haplosomes_[haplosome_index]; - - w *= _Fitness_HaploidChromosome(haplosome, p_mutationEffect_callbacks); - if (w <= 0.0) { - if (f_mutrunexps) chromosome->StopMutationRunExperimentClock("FitnessOfParent()"); - return 0.0; - } - - haplosome_index += 2; - break; - } - case ChromosomeType::kNullY_YSexChromosomeWithNull: - { - Haplosome *haplosome = individual->haplosomes_[haplosome_index+1]; - - w *= _Fitness_HaploidChromosome(haplosome, p_mutationEffect_callbacks); - if (w <= 0.0) { - if (f_mutrunexps) chromosome->StopMutationRunExperimentClock("FitnessOfParent()"); - return 0.0; - } - - haplosome_index += 2; - break; - } - } - - if (f_mutrunexps) chromosome->StopMutationRunExperimentClock("FitnessOfParent()"); - } - - return w; -} - -template double Subpopulation::FitnessOfParent(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); -template double Subpopulation::FitnessOfParent(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); -template double Subpopulation::FitnessOfParent(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); -template double Subpopulation::FitnessOfParent(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); -template double Subpopulation::FitnessOfParent(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); -template double Subpopulation::FitnessOfParent(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); - -double Subpopulation::FitnessOfParent_1CH_Diploid(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks) -{ - // calculate the fitness of the individual at index p_individual_index in the parent population - // this loops through all chromosomes, handling ploidy and callbacks as needed - double w = 1.0; - Individual *individual = parent_individuals_[p_individual_index]; - const int haplosome_index = 0; - - // diploid, possibly with one or both being null haplosomes - Haplosome *haplosome1 = individual->haplosomes_[haplosome_index]; - Haplosome *haplosome2 = individual->haplosomes_[haplosome_index+1]; - - w *= _Fitness_DiploidChromosome(haplosome1, haplosome2, p_mutationEffect_callbacks); - if (w <= 0.0) - return 0.0; - - return w; -} - -double Subpopulation::FitnessOfParent_1CH_Haploid(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks) -{ - // calculate the fitness of the individual at index p_individual_index in the parent population - // this loops through all chromosomes, handling ploidy and callbacks as needed - double w = 1.0; - Individual *individual = parent_individuals_[p_individual_index]; - const int haplosome_index = 0; - - // haploid, possibly null - Haplosome *haplosome = individual->haplosomes_[haplosome_index]; - - w *= _Fitness_HaploidChromosome(haplosome, p_mutationEffect_callbacks); - if (w <= 0.0) - return 0.0; - - return w; -} - -template -double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosome *haplosome2, std::vector &p_mutationEffect_callbacks) -{ - double w = 1.0; - bool haplosome1_null = haplosome1->IsNull(); - bool haplosome2_null = haplosome2->IsNull(); - -#if SLIM_USE_NONNEUTRAL_CACHES - int32_t nonneutral_change_counter = species_.nonneutral_change_counter_; - int32_t nonneutral_regime = species_.last_nonneutral_regime_; -#endif - - // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast - MutationType *single_callback_mut_type; - - if (f_singlecallback) - { - // our caller already did this lookup, to select this case, so this lookup is guaranteed to succeed - slim_objectid_t mutation_type_id = p_mutationEffect_callbacks[0]->mutation_type_id_; - - single_callback_mut_type = species_.MutationTypeWithID(mutation_type_id); - } - - MutationBlock *mutation_block = species_.SpeciesMutationBlock(); - Mutation *mut_block_ptr = mutation_block->mutation_buffer_; - - if (haplosome1_null && haplosome2_null) - { - // both haplosomes are null placeholders; no mutations, no fitness effects - return w; - } - else if (haplosome1_null || haplosome2_null) - { - // one haplosome is null, so we just need to scan through the non-null haplosome and account - // for its mutations, including the hemizygous dominance coefficient - const Haplosome *haplosome = haplosome1_null ? haplosome2 : haplosome1; - const int32_t mutrun_count = haplosome->mutrun_count_; - - for (int run_index = 0; run_index < mutrun_count; ++run_index) - { - const MutationRun *mutrun = haplosome->mutruns_[run_index]; - -#if SLIM_USE_NONNEUTRAL_CACHES - // Cache non-neutral mutations and read from the non-neutral buffers - const MutationIndex *haplosome_iter, *haplosome_max; - - mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); -#else - // Read directly from the MutationRun buffers - const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); - const MutationIndex *haplosome_max = mutrun->end_pointer_const(); -#endif - - // with an unpaired chromosome, we multiply each selection coefficient by the hemizygous dominance coefficient - // this is for a single X chromosome in a male, for example; dosage compensation, as opposed to heterozygosity - while (haplosome_iter != haplosome_max) - { - MutationIndex haplosome_mutindex = *haplosome_iter++; - Mutation *mutation = mut_block_ptr + haplosome_mutindex; - // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! - slim_effect_t cached_one_plus_hemizygousdom_sel = mutation_block->TraitInfoForIndex(haplosome_mutindex)[0].hemizygous_effect_; - - if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - { - w *= ApplyMutationEffectCallbacks(haplosome_mutindex, -1, cached_one_plus_hemizygousdom_sel, p_mutationEffect_callbacks, haplosome->individual_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= cached_one_plus_hemizygousdom_sel; - } - } - } - - return w; - } - else - { - // both haplosomes are being modeled, so we need to scan through and figure out which mutations are heterozygous and which are homozygous - const int32_t mutrun_count = haplosome1->mutrun_count_; - - for (int run_index = 0; run_index < mutrun_count; ++run_index) - { - const MutationRun *mutrun1 = haplosome1->mutruns_[run_index]; - const MutationRun *mutrun2 = haplosome2->mutruns_[run_index]; - -#if SLIM_USE_NONNEUTRAL_CACHES - // Cache non-neutral mutations and read from the non-neutral buffers - const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; - - mutrun1->beginend_nonneutral_pointers(mut_block_ptr, &haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); - mutrun2->beginend_nonneutral_pointers(mut_block_ptr, &haplosome2_iter, &haplosome2_max, nonneutral_change_counter, nonneutral_regime); -#else - // Read directly from the MutationRun buffers - const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); - const MutationIndex *haplosome2_iter = mutrun2->begin_pointer_const(); - - const MutationIndex *haplosome1_max = mutrun1->end_pointer_const(); - const MutationIndex *haplosome2_max = mutrun2->end_pointer_const(); -#endif - - // first, handle the situation before either haplosome iterator has reached the end of its haplosome, for simplicity/speed - if (haplosome1_iter != haplosome1_max && haplosome2_iter != haplosome2_max) - { - MutationIndex haplosome1_mutindex = *haplosome1_iter, haplosome2_mutindex = *haplosome2_iter; - slim_position_t haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_, haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; - - do - { - if (haplosome1_iter_position < haplosome2_iter_position) - { - // Process a mutation in haplosome1 since it is leading - Mutation *mutation = mut_block_ptr + haplosome1_mutindex; - // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! - slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].heterozygous_effect_; - - if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= cached_one_plus_dom_sel; - } - - if (++haplosome1_iter == haplosome1_max) - break; - else { - haplosome1_mutindex = *haplosome1_iter; - haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; - } - } - else if (haplosome1_iter_position > haplosome2_iter_position) - { - // Process a mutation in haplosome2 since it is leading - Mutation *mutation = mut_block_ptr + haplosome2_mutindex; - // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! - slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[0].heterozygous_effect_; - - if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - { - w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= cached_one_plus_dom_sel; - } - - if (++haplosome2_iter == haplosome2_max) - break; - else { - haplosome2_mutindex = *haplosome2_iter; - haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; - } - } - else - { - // Look for homozygosity: haplosome1_iter_position == haplosome2_iter_position - slim_position_t position = haplosome1_iter_position; - const MutationIndex *haplosome1_start = haplosome1_iter; - - // advance through haplosome1 as long as we remain at the same position, handling one mutation at a time - do - { - const MutationIndex *haplosome2_matchscan = haplosome2_iter; - - // advance through haplosome2 with haplosome2_matchscan, looking for a match for the current mutation in haplosome1, to determine whether we are homozygous or not - while (haplosome2_matchscan != haplosome2_max && (mut_block_ptr + *haplosome2_matchscan)->position_ == position) - { - if (haplosome1_mutindex == *haplosome2_matchscan) - { - // a match was found, so we multiply our fitness by the full selection coefficient - Mutation *mutation = mut_block_ptr + haplosome1_mutindex; - // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! - slim_effect_t cached_one_plus_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].homozygous_effect_; - - if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, true, cached_one_plus_sel, p_mutationEffect_callbacks, haplosome1->individual_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= cached_one_plus_sel; - } - goto homozygousExit1; - } - - haplosome2_matchscan++; - } - - // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient - { - Mutation *mutation = mut_block_ptr + haplosome1_mutindex; - // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! - slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].heterozygous_effect_; - - if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= cached_one_plus_dom_sel; - } - } - - homozygousExit1: - - if (++haplosome1_iter == haplosome1_max) - break; - else { - haplosome1_mutindex = *haplosome1_iter; - haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; - } - } while (haplosome1_iter_position == position); - - // advance through haplosome2 as long as we remain at the same position, handling one mutation at a time - do - { - const MutationIndex *haplosome1_matchscan = haplosome1_start; - - // advance through haplosome1 with haplosome1_matchscan, looking for a match for the current mutation in haplosome2, to determine whether we are homozygous or not - while (haplosome1_matchscan != haplosome1_max && (mut_block_ptr + *haplosome1_matchscan)->position_ == position) - { - if (haplosome2_mutindex == *haplosome1_matchscan) - { - // a match was found; we know this match was already found by the haplosome1 loop above, so our fitness has already been multiplied appropriately - goto homozygousExit2; - } - - haplosome1_matchscan++; - } - - // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient - { - Mutation *mutation = mut_block_ptr + haplosome2_mutindex; - // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! - slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[0].heterozygous_effect_; - - if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - { - w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= cached_one_plus_dom_sel; - } - } - - homozygousExit2: - - if (++haplosome2_iter == haplosome2_max) - break; - else { - haplosome2_mutindex = *haplosome2_iter; - haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; - } - } while (haplosome2_iter_position == position); - - // break out if either haplosome has reached its end - if (haplosome1_iter == haplosome1_max || haplosome2_iter == haplosome2_max) - break; - } - } while (true); - } - - // one or the other haplosome has now reached its end, so now we just need to handle the remaining mutations in the unfinished haplosome -#if DEBUG - assert(!(haplosome1_iter != haplosome1_max && haplosome2_iter != haplosome2_max)); -#endif - - // if haplosome1 is unfinished, finish it - while (haplosome1_iter != haplosome1_max) - { - MutationIndex haplosome1_mutindex = *haplosome1_iter++; - Mutation *mutation = mut_block_ptr + haplosome1_mutindex; - // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! - slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[0].heterozygous_effect_; - - if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - { - w *= ApplyMutationEffectCallbacks(haplosome1_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= cached_one_plus_dom_sel; - } - } - - // if haplosome2 is unfinished, finish it - while (haplosome2_iter != haplosome2_max) - { - MutationIndex haplosome2_mutindex = *haplosome2_iter++; - Mutation *mutation = mut_block_ptr + haplosome2_mutindex; - // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! - slim_effect_t cached_one_plus_dom_sel = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[0].heterozygous_effect_; - - if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - { - w *= ApplyMutationEffectCallbacks(haplosome2_mutindex, false, cached_one_plus_dom_sel, p_mutationEffect_callbacks, haplosome1->individual_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= cached_one_plus_dom_sel; - } - } - } - - return w; - } -} - -template double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosome *haplosome2, std::vector &p_mutationEffect_callbacks); -template double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosome *haplosome2, std::vector &p_mutationEffect_callbacks); -template double Subpopulation::_Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosome *haplosome2, std::vector &p_mutationEffect_callbacks); - -template -double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vector &p_mutationEffect_callbacks) -{ - if (haplosome->IsNull()) - { - // the haplosome is a null placeholder; no mutations, no fitness effects - return 1.0; - } - else - { - // we just need to scan through the haplosome and account for its mutations, - // using the homozygous fitness effect (no dominance effects with haploidy) -#if SLIM_USE_NONNEUTRAL_CACHES - int32_t nonneutral_change_counter = species_.nonneutral_change_counter_; - int32_t nonneutral_regime = species_.last_nonneutral_regime_; -#endif - - // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast - MutationType *single_callback_mut_type; - - if (f_singlecallback) - { - // our caller already did this lookup, to select this case, so this lookup is guaranteed to succeed - slim_objectid_t mutation_type_id = p_mutationEffect_callbacks[0]->mutation_type_id_; - - single_callback_mut_type = species_.MutationTypeWithID(mutation_type_id); - } - - MutationBlock *mutation_block = species_.SpeciesMutationBlock(); - Mutation *mut_block_ptr = mutation_block->mutation_buffer_; - const int32_t mutrun_count = haplosome->mutrun_count_; - double w = 1.0; - - for (int run_index = 0; run_index < mutrun_count; ++run_index) - { - const MutationRun *mutrun = haplosome->mutruns_[run_index]; - -#if SLIM_USE_NONNEUTRAL_CACHES - // Cache non-neutral mutations and read from the non-neutral buffers - const MutationIndex *haplosome_iter, *haplosome_max; - - mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); -#else - // Read directly from the MutationRun buffers - const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); - const MutationIndex *haplosome_max = mutrun->end_pointer_const(); -#endif - - // with a haploid chromosome, we use the homozygous fitness effect - while (haplosome_iter != haplosome_max) - { - MutationIndex haplosome_mutation = *haplosome_iter++; - Mutation *mutation = (mut_block_ptr + haplosome_mutation); - // FIXME MULTICHROM: This method needs to be extended to support trait indices other than zero; and additive traits also! - slim_effect_t cached_one_plus_sel = mutation_block->TraitInfoForIndex(haplosome_mutation)[0].homozygous_effect_; - - if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - { - w *= ApplyMutationEffectCallbacks(haplosome_mutation, -1, cached_one_plus_sel, p_mutationEffect_callbacks, haplosome->individual_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= cached_one_plus_sel; - } - } - } - - return w; - } -} - -template double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vector &p_mutationEffect_callbacks); -template double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vector &p_mutationEffect_callbacks); -template double Subpopulation::_Fitness_HaploidChromosome(Haplosome *haplosome, std::vector &p_mutationEffect_callbacks); - // WF only: void Subpopulation::TallyLifetimeReproductiveOutput(void) { @@ -4235,7 +2989,7 @@ template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -Individual *Subpopulation::GenerateIndividualEmpty(slim_popsize_t p_individual_index, IndividualSex p_child_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age, bool p_haplosome1_null, bool p_haplosome2_null, bool p_run_modify_child, bool p_record_in_treeseq) +Individual *Subpopulation::GenerateIndividualEmpty(slim_popsize_t p_individual_index, IndividualSex p_child_sex, slim_age_t p_age, slim_fitness_t p_fitness, float p_mean_parent_age, bool p_haplosome1_null, bool p_haplosome2_null, bool p_run_modify_child, bool p_record_in_treeseq) { // Create the offspring and record it Individual *individual = NewSubpopIndividual(p_individual_index, p_child_sex, p_age, p_fitness, p_mean_parent_age); @@ -5912,7 +4666,7 @@ void Subpopulation::MergeReproductionOffspring(void) } // nonWF only: -bool Subpopulation::ApplySurvivalCallbacks(std::vector &p_survival_callbacks, Individual *p_individual, double p_fitness, double p_draw, bool p_surviving) +bool Subpopulation::ApplySurvivalCallbacks(std::vector &p_survival_callbacks, Individual *p_individual, slim_fitness_t p_fitness, double p_draw, bool p_surviving) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::ApplySurvivalCallbacks(): running Eidos callback"); @@ -5959,7 +4713,7 @@ bool Subpopulation::ApplySurvivalCallbacks(std::vector &p_survi // This code is similar to Population::ExecuteScript, but we set up an additional symbol table, and we use the return value { // local variables for the callback parameters that we might need to allocate here, and thus need to free below - EidosValue_Float local_fitness(p_fitness); + EidosValue_Float local_fitness((double)p_fitness); EidosValue_Float local_draw(p_draw); // We need to actually execute the script; we start a block here to manage the lifetime of the symbol table @@ -6117,12 +4871,12 @@ void Subpopulation::ViabilitySurvival(std::vector &p_survival_c for (int individual_index = 0; individual_index < parent_subpop_size_; ++individual_index) { Individual *individual = individual_data[individual_index]; - double fitness = individual->cached_fitness_UNSAFE_; // never overridden in nonWF models, so this is safe with no check + slim_fitness_t fitness = individual->cached_fitness_UNSAFE_; // never overridden in nonWF models, so this is safe with no check uint8_t survived; - if (fitness <= 0.0) survived = false; - else if (fitness >= 1.0) survived = true; - else survived = (Eidos_rng_uniform_doubleCO(rng_64) < fitness); + if (fitness <= (slim_fitness_t)0.0) survived = false; + else if (fitness >= (slim_fitness_t)1.0) survived = true; + else survived = (Eidos_rng_uniform_doubleCO(rng_64) < (double)fitness); survival_buf_perthread[individual_index] = survived; } @@ -6139,9 +4893,9 @@ void Subpopulation::ViabilitySurvival(std::vector &p_survival_c { slim_popsize_t individual_index = shuffle_buf[shuffle_index]; Individual *individual = individual_data[individual_index]; - double fitness = individual->cached_fitness_UNSAFE_; // never overridden in nonWF models, so this is safe with no check + slim_fitness_t fitness = individual->cached_fitness_UNSAFE_; // never overridden in nonWF models, so this is safe with no check double draw = Eidos_rng_uniform_doubleCO(rng_64); // always need a draw to pass to the callback - uint8_t survived = (draw < fitness); + uint8_t survived = (draw < (double)fitness); // run the survival() callbacks to allow the above decision to be modified survived = ApplySurvivalCallbacks(p_survival_callbacks, individual, fitness, draw, survived); @@ -6495,7 +5249,7 @@ EidosValue_SP Subpopulation::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(tag_value)); } case gID_fitnessScaling: // ACCELERATED - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(subpop_fitness_scaling_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)subpop_fitness_scaling_)); // all others, including gID_none default: @@ -6576,7 +5330,7 @@ EidosValue *Subpopulation::GetProperty_Accelerated_fitnessScaling(EidosGlobalStr { Subpopulation *value = (Subpopulation *)(p_values[value_index]); - float_result->set_float_no_check(value->subpop_fitness_scaling_, value_index); + float_result->set_float_no_check((double)value->subpop_fitness_scaling_, value_index); } return float_result; @@ -6595,9 +5349,9 @@ void Subpopulation::SetProperty(EidosGlobalStringID p_property_id, const EidosVa } case gID_fitnessScaling: // ACCELERATED { - subpop_fitness_scaling_ = p_value.FloatAtIndex_NOCAST(0, nullptr); + subpop_fitness_scaling_ = (slim_fitness_t)p_value.FloatAtIndex_NOCAST(0, nullptr); - if ((subpop_fitness_scaling_ < 0.0) || std::isnan(subpop_fitness_scaling_)) + if ((subpop_fitness_scaling_ < (slim_fitness_t)0.0) || std::isnan(subpop_fitness_scaling_)) EIDOS_TERMINATION << "ERROR (Subpopulation::SetProperty): property fitnessScaling must be >= 0.0." << EidosTerminate(); return; @@ -6649,9 +5403,9 @@ void Subpopulation::SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p #pragma unused (p_property_id) if (p_source_size == 1) { - double source_value = p_source.FloatAtIndex_NOCAST(0, nullptr); + slim_fitness_t source_value = (slim_fitness_t)p_source.FloatAtIndex_NOCAST(0, nullptr); - if ((source_value < 0.0) || std::isnan(source_value)) + if ((source_value < 0.0f) || std::isnan(source_value)) EIDOS_TERMINATION << "ERROR (Subpopulation::SetProperty_Accelerated_fitnessScaling): property fitnessScaling must be >= 0.0." << EidosTerminate(); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -6663,9 +5417,9 @@ void Subpopulation::SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p for (size_t value_index = 0; value_index < p_values_size; ++value_index) { - double source_value = source_data[value_index]; + slim_fitness_t source_value = (slim_fitness_t)source_data[value_index]; - if ((source_value < 0.0) || std::isnan(source_value)) + if ((source_value < 0.0f) || std::isnan(source_value)) EIDOS_TERMINATION << "ERROR (Subpopulation::SetProperty_Accelerated_fitnessScaling): property fitnessScaling must be >= 0.0." << EidosTerminate(); ((Subpopulation *)(p_values[value_index]))->subpop_fitness_scaling_ = source_value; @@ -11532,7 +10286,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_cachedFitness(EidosGlobalStringID p_m EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_cachedFitness): cachedFitness() index " << index << " out of range." << EidosTerminate(); } - double fitness = (individual_cached_fitness_OVERRIDE_ ? individual_cached_fitness_OVERRIDE_value_ : parent_individuals_[index]->cached_fitness_UNSAFE_); + double fitness = (individual_cached_fitness_OVERRIDE_ ? individual_cached_fitness_OVERRIDE_value_ : (double)parent_individuals_[index]->cached_fitness_UNSAFE_); float_return->set_float_no_check(fitness, value_index); } diff --git a/core/subpopulation.h b/core/subpopulation.h index a41fe08f..23cc5c63 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -90,10 +90,9 @@ class Subpopulation : public EidosDictionaryUnretained private: - // WF only: - gsl_ran_discrete_t *lookup_parent_ = nullptr; // OWNED POINTER: lookup table for drawing a parent based upon fitness - gsl_ran_discrete_t *lookup_female_parent_ = nullptr; // OWNED POINTER: lookup table for drawing a female parent based upon fitness, SEX ONLY - gsl_ran_discrete_t *lookup_male_parent_ = nullptr; // OWNED POINTER: lookup table for drawing a male parent based upon fitness, SEX ONLY + gsl_ran_discrete_t *lookup_parent_ = nullptr; // OWNED POINTER: WF ONLY; lookup table for drawing a parent based upon fitness + gsl_ran_discrete_t *lookup_female_parent_ = nullptr; // OWNED POINTER: WF ONLY; lookup table for drawing a female parent based upon fitness, SEX ONLY + gsl_ran_discrete_t *lookup_male_parent_ = nullptr; // OWNED POINTER: WF ONLY; lookup table for drawing a male parent based upon fitness, SEX ONLY EidosSymbolTableEntry self_symbol_; // for fast setup of the symbol table @@ -174,13 +173,12 @@ class Subpopulation : public EidosDictionaryUnretained std::vector per_trait_subpop_caches_; // one entry per trait, indexed by trait index // WF only: - // Fitness caching. Every individual now caches its fitness internally, and that is what is used by SLiMgui and by the cachedFitness() method of Subpopulation. - // These fitness cache buffers are additional to that, used only in WF models. They are used for two things. First, as the data source for setting up our lookup - // objects for drawing mates by fitness; the GSL wants that data to be in the form of a single buffer. And second, by mateChoice() callbacks, which throw around - // vectors of weights, and want to have default weight vectors for the non-sex and sex cases. In nonWF models these buffers are not used, and not even set up. - // In WF models we could continue to use these buffers for all uses (i.e., SLiMgui and cachedFitness()), but to keep the code simple it seems better to use the - // caches in Individual for both WF and nonWF models where possible. - double *cached_parental_fitness_ = nullptr; // OWNED POINTER: cached in UpdateFitness() + // Fitness caching. Every individual now caches its fitness internally, and that is what is used by SLiMgui, the cachedFitness() method of Subpopulation, etc. + // These fitness cache buffers are additional to that, used only in WF models. They are now used for only one thing: as the data source for setting up our lookup + // objects for drawing mates by fitness; the GSL wants that data to be in the form of a single buffer. In nonWF models these buffers are not used, and not even set + // up. BCH 12/30/2025: up through SLiM 5.1 these buffers were also maintained for the use of mateChoice() callbacks in ApplyMateChoiceCallbacks(), as the data + // source for the `weights` pseudo-parameter; after SLiM 5.1 that method allocates these buffers itself, lazily, if they are not already present. + double *cached_parental_fitness_ = nullptr; // OWNED POINTER: cached in UpdateWFFitnessBuffers() double *cached_male_fitness_ = nullptr; // OWNED POINTER: SEX ONLY: same as cached_parental_fitness_ but with 0 for all females slim_popsize_t cached_fitness_size_ = 0; // the size (number of entries used) of cached_parental_fitness_ and cached_male_fitness_ slim_popsize_t cached_fitness_capacity_ = 0; // the capacity of the malloced buffers cached_parental_fitness_ and cached_male_fitness_ @@ -190,9 +188,9 @@ class Subpopulation : public EidosDictionaryUnretained // When a model is neutral or nearly neutral, every individual may have the same fitness value, and we may know that. In such cases, we want to avoid setting // up the cached fitness value in each individual; instead, we set a flag here that overrides cached_fitness_UNSAFE_ and says it has the same value for all. // This happens only for the parental generation's cached fitness values (cached fitness values for the child generation should never be accessed by anyone - // since they are not valid). And it happens only in WF models, since in nonWF models the generational overlap makes this scheme impossible. A somewhat special - // case, then, but it seems worthwhile since the penalty for seting every cached fitness value, and then gathering them back up again in UpdateWFFitnessBuffers(), - // is large – almost 20% of total runtime for the full Gravel model, for example. Neutral WF models are common and worth special-casing. + // since they are not valid). This is used only in WF models because it doesn't seem very useful in nonWF models; ViabilitySurvival() therefore assumes it. + // A somewhat special case, then, but it seems worthwhile since the penalty for setting every cached fitness value, and then gathering them back up again in + // UpdateWFFitnessBuffers(), is large – ~20% of total runtime for the full Gravel model, for example. Neutral WF models are common and worth special-casing. bool individual_cached_fitness_OVERRIDE_ = false; double individual_cached_fitness_OVERRIDE_value_; @@ -207,7 +205,7 @@ class Subpopulation : public EidosDictionaryUnretained slim_usertag_t tag_value_ = SLIM_TAG_UNSET_VALUE; // a user-defined tag value - double subpop_fitness_scaling_ = 1.0; // the fitnessScaling property value + slim_fitness_t subpop_fitness_scaling_ = 1.0; // the fitnessScaling property value #ifdef SLIMGUI bool gui_selected_ = false; // keeps track of whether we are selected in SLiMgui's table of subpopulations @@ -267,7 +265,7 @@ class Subpopulation : public EidosDictionaryUnretained slim_popsize_t DrawFemaleParentEqualProbability(EidosRNG_32_bit &rng_32) const; // draw a female from the subpopulation with equal probabilities; SEX ONLY slim_popsize_t DrawMaleParentEqualProbability(EidosRNG_32_bit &rng_32) const; // draw a male from the subpopulation with equal probabilities; SEX ONLY - inline __attribute__((always_inline)) Individual *NewSubpopIndividual(slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age) + inline __attribute__((always_inline)) Individual *NewSubpopIndividual(slim_popsize_t p_individual_index, IndividualSex p_sex, slim_age_t p_age, slim_fitness_t p_fitness, float p_mean_parent_age) { if (individuals_junkyard_.size()) { @@ -382,21 +380,14 @@ class Subpopulation : public EidosDictionaryUnretained #if (defined(_OPENMP) && SLIM_USE_NONNEUTRAL_CACHES) void FixNonNeutralCaches_OMP(void); #endif - void UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks); // update fitness values based upon current mutations - - // calculate the fitness of a given individual; the x dominance coeff is used only if the X is modeled - template - double FitnessOfParent(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); - double FitnessOfParent_1CH_Diploid(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); - double FitnessOfParent_1CH_Haploid(slim_popsize_t p_individual_index, std::vector &p_mutationEffect_callbacks); - template - double _Fitness_HaploidChromosome(Haplosome *haplosome, std::vector &p_mutationEffect_callbacks); - template - double _Fitness_DiploidChromosome(Haplosome *haplosome1, Haplosome *haplosome2, std::vector &p_mutationEffect_callbacks); + void UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); + + template + void _UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); - double ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, double p_computed_fitness, std::vector &p_mutationEffect_callbacks, Individual *p_individual); - double ApplyFitnessEffectCallbacks(std::vector &p_fitnessEffect_callbacks, slim_popsize_t p_individual_index); + slim_effect_t ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual); + slim_fitness_t ApplyFitnessEffectCallbacks(std::vector &p_fitnessEffect_callbacks, Individual *p_individual); // generate newly allocated offspring individuals from parent individuals; these methods loop over // chromosomes/haplosomes, and are templated for speed, providing a set of optimized variants @@ -409,7 +400,7 @@ class Subpopulation : public EidosDictionaryUnretained template Individual *GenerateIndividualCloned(Individual *p_parent); - Individual *GenerateIndividualEmpty(slim_popsize_t p_individual_index, IndividualSex p_child_sex, slim_age_t p_age, double p_fitness, float p_mean_parent_age, bool p_haplosome1_null, bool p_haplosome2_null, bool p_run_modify_child, bool p_record_in_treeseq); + Individual *GenerateIndividualEmpty(slim_popsize_t p_individual_index, IndividualSex p_child_sex, slim_age_t p_age, slim_fitness_t p_fitness, float p_mean_parent_age, bool p_haplosome1_null, bool p_haplosome2_null, bool p_run_modify_child, bool p_record_in_treeseq); // these WF-only "munge" variants munge an existing individual into the new child, reusing the individual // and its haplosome objects; they are all templated for speed, providing variants for different milieux @@ -437,7 +428,7 @@ class Subpopulation : public EidosDictionaryUnretained // WF only: void WipeIndividualsAndHaplosomes(std::vector &p_individuals, slim_popsize_t p_individual_count, slim_popsize_t p_first_male); void GenerateChildrenToFitWF(void); // given the set subpop size and sex ratio, configure the child generation haplosomes and individuals to fit - void UpdateWFFitnessBuffers(bool p_pure_neutral); // update the WF model fitness buffers after UpdateFitness() + void UpdateWFFitnessBuffers(void); // update the WF model fitness buffers after UpdateFitness() void TallyLifetimeReproductiveOutput(void); void SwapChildAndParentHaplosomes(void); // switch to the next generation by swapping; the children become the parents @@ -445,7 +436,7 @@ class Subpopulation : public EidosDictionaryUnretained void ApplyReproductionCallbacks(std::vector &p_reproduction_callbacks, slim_popsize_t p_individual_index); void ReproduceSubpopulation(void); void MergeReproductionOffspring(void); - bool ApplySurvivalCallbacks(std::vector &p_survival_callbacks, Individual *p_individual, double p_fitness, double p_draw, bool p_surviving); + bool ApplySurvivalCallbacks(std::vector &p_survival_callbacks, Individual *p_individual, slim_fitness_t p_fitness, double p_draw, bool p_surviving); void ViabilitySurvival(std::vector &p_survival_callbacks); void IncrementIndividualAges(void); diff --git a/core/substitution.cpp b/core/substitution.cpp index f1a9304b..9cb6290b 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -75,7 +75,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // We need to infer the values of the is_neutral_ and is_independent_dominance_ flags // FIXME MULTITRAIT: needs to be fixed when the below issues are fixed - is_neutral_ = (p_selection_coeff == 0.0); + is_neutral_ = (p_selection_coeff == (slim_effect_t)0.0); is_independent_dominance_ = std::isnan(p_dominance_coeff); trait_info_[0].effect_size_ = p_selection_coeff; @@ -121,7 +121,7 @@ void Substitution::SelfConsistencyCheck(const std::string &p_message_end) (!is_independent_dominance_ && std::isnan(traitInfoRec.dominance_coeff_UNSAFE_))) EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution independent dominance state is inconsistent" << p_message_end << "." << EidosTerminate(); - if (traitInfoRec.effect_size_ != 0.0) + if (traitInfoRec.effect_size_ != (slim_effect_t)0.0) all_neutral_effects = false; } @@ -153,7 +153,7 @@ slim_effect_t Substitution::RealizedDominanceForTrait(Trait *p_trait) if (effect_size == (slim_effect_t)0.0) return (slim_effect_t)0.5; - if (effect_size <= -1.0) + if (effect_size <= (slim_effect_t)-1.0) return (slim_effect_t)1.0; // do the math in double-precision float to avoid numerical error @@ -314,7 +314,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[0].effect_size_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[0].effect_size_)); else if (trait_count == 0) return gStaticEidosValue_Float_ZeroVec; else @@ -325,7 +325,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) { slim_effect_t effect = trait_info_[trait_index].effect_size_; - float_result->push_float_no_check(effect); + float_result->push_float_no_check((double)effect); } return EidosValue_SP(float_result); @@ -344,7 +344,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) { slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[0]); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)realized_dominance)); } else if (trait_count == 0) { @@ -358,7 +358,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) { slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[trait_index]); - float_result->push_float_no_check(realized_dominance); + float_result->push_float_no_check((double)realized_dominance); } return EidosValue_SP(float_result); @@ -373,7 +373,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) size_t trait_count = traits.size(); if (trait_count == 1) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[0].hemizygous_dominance_coeff_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[0].hemizygous_dominance_coeff_)); else if (trait_count == 0) return gStaticEidosValue_Float_ZeroVec; else @@ -384,7 +384,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) { slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; - float_result->push_float_no_check(dominance); + float_result->push_float_no_check((double)dominance); } return EidosValue_SP(float_result); @@ -453,7 +453,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) Trait *trait = species.TraitFromName(trait_name); if (trait) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].effect_size_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[trait->Index()].effect_size_)); } else if ((property_string.length() > 19) && Eidos_string_hasSuffix(property_string, "HemizygousDominance")) { @@ -461,7 +461,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) Trait *trait = species.TraitFromName(trait_name); if (trait) - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_info_[trait->Index()].hemizygous_dominance_coeff_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[trait->Index()].hemizygous_dominance_coeff_)); } else if ((property_string.length() > 9) && Eidos_string_hasSuffix(property_string, "Dominance")) { @@ -473,7 +473,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) // Note that we use RealizedDominanceForTrait() here so that an independent dominance of NAN gets handled. slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)realized_dominance)); } } @@ -720,7 +720,7 @@ EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_m int64_t trait_index = trait_indices[0]; slim_effect_t effect = trait_info_[trait_index].effect_size_; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(effect)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)effect)); } else { @@ -730,7 +730,7 @@ EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_m { slim_effect_t effect = trait_info_[trait_index].effect_size_; - float_result->push_float_no_check(effect); + float_result->push_float_no_check((double)effect); } return EidosValue_SP(float_result); @@ -756,7 +756,7 @@ EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID Trait *trait = traits[trait_index]; slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(realized_dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)realized_dominance)); } else { @@ -767,7 +767,7 @@ EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID Trait *trait = traits[trait_index]; slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); - float_result->push_float_no_check(realized_dominance); + float_result->push_float_no_check((double)realized_dominance); } return EidosValue_SP(float_result); @@ -791,7 +791,7 @@ EidosValue_SP Substitution::ExecuteMethod_hemizygousDominanceForTrait(EidosGloba int64_t trait_index = trait_indices[0]; slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(dominance)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)dominance)); } else { @@ -801,7 +801,7 @@ EidosValue_SP Substitution::ExecuteMethod_hemizygousDominanceForTrait(EidosGloba { slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; - float_result->push_float_no_check(dominance); + float_result->push_float_no_check((double)dominance); } return EidosValue_SP(float_result); diff --git a/core/trait.cpp b/core/trait.cpp index 9971b42d..b19c46eb 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -25,7 +25,7 @@ Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, sl EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property individualOffsetSD requires a finite value (not NAN or INF)." << EidosTerminate(); // effects for multiplicative traits clip at 0.0 - if ((type_ == TraitType::kMultiplicative) && (p_baselineOffset < 0.0)) + if ((type_ == TraitType::kMultiplicative) && (p_baselineOffset < (slim_effect_t)0.0)) baselineOffset_ = 0.0; else baselineOffset_ = p_baselineOffset; @@ -43,7 +43,7 @@ void Trait::_RecacheIndividualOffsetDistribution(void) // effects for multiplicative traits clip at 0.0 slim_effect_t offset = static_cast(individualOffsetMean_); - if ((type_ == TraitType::kMultiplicative) && (offset < 0.0)) + if ((type_ == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) individualOffsetFixedValue_ = 0.0; else individualOffsetFixedValue_ = offset; @@ -78,7 +78,7 @@ slim_effect_t Trait::_DrawIndividualOffset(void) const slim_effect_t offset = static_cast(gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_); // effects for multiplicative traits clip at 0.0 - if ((type_ == TraitType::kMultiplicative) && (offset < 0.0)) + if ((type_ == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) offset = 0.0; return offset; @@ -129,7 +129,7 @@ EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) // variables case gID_baselineOffset: { - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(baselineOffset_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)baselineOffset_)); } case gID_directFitnessEffect: { @@ -251,7 +251,7 @@ const std::vector *Trait_Class::Properties(void) con properties = new std::vector(*super::Properties()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_baselineOffset, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_directFitnessEffect, false, kEidosValueMaskLogical | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_directFitnessEffect, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_index, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_individualOffsetMean, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_individualOffsetSD, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); diff --git a/core/trait.h b/core/trait.h index 989c3541..3b833bc8 100644 --- a/core/trait.h +++ b/core/trait.h @@ -94,7 +94,9 @@ class Trait : public EidosDictionaryRetained void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetFixed_ and individualOffsetFixedValue_ slim_effect_t _DrawIndividualOffset(void) const; // draws from a normal distribution defined by individualOffsetMean_ and individualOffsetSD_ - inline slim_effect_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } + inline __attribute__((always_inline)) slim_effect_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } + + inline __attribute__((always_inline)) bool HasDirectFitnessEffect(void) const { return directFitnessEffect_; } // diff --git a/eidos/eidos_functions_colors.cpp b/eidos/eidos_functions_colors.cpp index 89a21be8..fc1c4ba8 100644 --- a/eidos/eidos_functions_colors.cpp +++ b/eidos/eidos_functions_colors.cpp @@ -136,7 +136,7 @@ EidosValue_SP Eidos_ExecuteFunction_color2rgb(const std::vector & // returns a vector Eidos_GetColorComponents(color_value->StringRefAtIndex_NOCAST(0, nullptr), &r, &g, &b); - result_SP = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float{r, g, b}); + result_SP = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float{(double)r, (double)g, (double)b}); } else { @@ -147,9 +147,9 @@ EidosValue_SP Eidos_ExecuteFunction_color2rgb(const std::vector & for (int value_index = 0; value_index < color_count; ++value_index) { Eidos_GetColorComponents(color_value->StringRefAtIndex_NOCAST(value_index, nullptr), &r, &g, &b); - float_result->set_float_no_check(r, value_index); - float_result->set_float_no_check(g, value_index + color_count); - float_result->set_float_no_check(b, value_index + color_count + color_count); + float_result->set_float_no_check((double)r, value_index); + float_result->set_float_no_check((double)g, value_index + color_count); + float_result->set_float_no_check((double)b, value_index + color_count + color_count); } const int64_t dim_buf[2] = {color_count, 3}; diff --git a/eidos/eidos_globals.cpp b/eidos/eidos_globals.cpp index aee9b874..8dff8c89 100644 --- a/eidos/eidos_globals.cpp +++ b/eidos/eidos_globals.cpp @@ -3088,7 +3088,7 @@ double Eidos_TTest_TwoSampleWelch(const double *p_set1, int p_count1, const doub if ((p_count1 <= 1) || (p_count2 <= 1)) { std::cout << "Eidos_TTest_TwoSampleWelch requires enough elements to compute variance" << std::endl; - return NAN; + return std::numeric_limits::quiet_NaN(); } // Compute measurements @@ -3123,7 +3123,7 @@ double Eidos_TTest_TwoSampleWelch(const double *p_set1, int p_count1, const doub // To avoid divisions by 0: if (var1 + var2 == 0) - return NAN; + return std::numeric_limits::quiet_NaN(); // two-sample test double t = (mean1 - mean2) / sqrt(var1 / p_count1 + var2 / p_count2); @@ -3146,7 +3146,7 @@ double Eidos_TTest_OneSample(const double *p_set1, int p_count1, double p_mu, do if (p_count1 <= 1) { std::cout << "Eidos_TTest_OneSample requires enough elements to compute variance" << std::endl; - return NAN; + return std::numeric_limits::quiet_NaN(); } // Compute measurements @@ -3169,7 +3169,7 @@ double Eidos_TTest_OneSample(const double *p_set1, int p_count1, double p_mu, do // To avoid divisions by 0: if (var1 == 0) - return NAN; + return std::numeric_limits::quiet_NaN(); // one-sample test double t = (mean1 - p_mu) / (sqrt(var1) / sqrt(p_count1)); diff --git a/eidos/eidos_globals.h b/eidos/eidos_globals.h index 558ee7e4..8e11de05 100644 --- a/eidos/eidos_globals.h +++ b/eidos/eidos_globals.h @@ -48,6 +48,13 @@ #define CHRONO_PROFILING #endif +// +// Turn on warnings that we want on in Eidos and SLiM code, but not in other code such as the GSL +// +#if (!defined(EIDOS_GUI) && !defined(SLIMGUI)) +#pragma GCC diagnostic warning "-Wdouble-promotion" +#endif + #include "eidos_openmp.h" #include "eidos_intrusive_ptr.h" diff --git a/eidos/eidos_test_functions_math.cpp b/eidos/eidos_test_functions_math.cpp index 3ecb47f8..f5767999 100644 --- a/eidos/eidos_test_functions_math.cpp +++ b/eidos/eidos_test_functions_math.cpp @@ -1088,10 +1088,10 @@ void _RunFunctionMathTests_s_through_z(void) // sqrt() EidosAssertScriptSuccess_F("sqrt(64);", 8); EidosAssertScriptSuccess_L("isNAN(sqrt(-64));", true); - EidosAssertScriptSuccess_FV("sqrt(c(4, -16, 9, 1024));", {2, NAN, 3, 32}); + EidosAssertScriptSuccess_FV("sqrt(c(4, -16, 9, 1024));", {2, std::numeric_limits::quiet_NaN(), 3, 32}); EidosAssertScriptSuccess_F("sqrt(64.0);", 8); EidosAssertScriptSuccess_L("isNAN(sqrt(-64.0));", true); - EidosAssertScriptSuccess_FV("sqrt(c(4.0, -16.0, 9.0, 1024.0));", {2, NAN, 3, 32}); + EidosAssertScriptSuccess_FV("sqrt(c(4.0, -16.0, 9.0, 1024.0));", {2, std::numeric_limits::quiet_NaN(), 3, 32}); EidosAssertScriptRaise("sqrt(T);", 0, "cannot be type"); EidosAssertScriptRaise("sqrt('foo');", 0, "cannot be type"); EidosAssertScriptRaise("sqrt(_Test(7));", 0, "cannot be type"); From 7c992c672ad72d0f9ecef360afbd90cbadebcaa4 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 31 Dec 2025 15:57:31 -0600 Subject: [PATCH 050/107] fix CI warnings and errors, fix recalculateFitness() --- QtSLiM/help/SLiMHelpClasses.html | 3 ++- SLiMgui/SLiMHelpClasses.rtf | 27 +++++++++++++++++++++++++-- VERSIONS | 1 + core/community.cpp | 4 ++-- core/haplosome.cpp | 2 +- core/mutation.cpp | 8 ++++---- core/population.cpp | 6 +++--- core/population.h | 2 +- core/species.cpp | 6 +++--- core/species.h | 2 +- core/species_eidos.cpp | 16 +++++++++++----- core/subpopulation.cpp | 9 +++++++-- core/subpopulation.h | 2 +- eidos/eidos_simd.h | 9 +++++++++ 14 files changed, 71 insertions(+), 26 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index b942e4f1..e4e7b9f1 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -1093,9 +1093,10 @@

As of SLiM 5.0, this method will read and restore substitutions if they were saved by outputFull() with its substitutions=T option.  This facility is only available when reading binary output from outputFull(), as chosen by its binary=T option; otherwise, an error will result.

This method can also be used to read tree-sequence information, in the form of single-chromosome .trees files and multi-chromosome trees archives, as saved by treeSeqOutput() or generated by the Python pyslim package.  Note that the user metadata for a tree-sequence file can be read separately with the treeSeqMetadata() function.  Beginning with SLiM 4, the subpopMap parameter may be supplied to re-order the populations of the input tree sequence when it is loaded in to SLiM.  This parameter must have a value that is a Dictionary; the keys of this dictionary should be SLiM population identifiers as string values (e.g., "p2"), and the values should be indexes of populations in the input tree sequence; a key/value pair of "p2", 4 would mean that the fifth population in the input (the one at zero-based index 4) should become p2 on loading into SLiM.  If subpopMap is non-NULL, all populations in the tree sequence must be explicitly mapped, even if their index will not change and even if they will not be used by SLiM; the only exception is for unused slots in the population table, which can be explicitly remapped but do not have to be.  For instance, suppose we have a tree sequence in which population 0 is unused, population 1 is not a SLiM population (for example, an ancestral population produced by msprime), and population 2 is a SLiM population, and we want to load this in with population 2 as p0 in SLiM.  To do this, we could supply a value of Dictionary("p0", 2, "p1", 1, "p2", 0) for subpopMap, or we could leave out slot 0 since it is unused, with Dictionary("p0", 2, "p1", 1).  Although this facility cannot be used to remove populations in the tree sequence, note that it may add populations that will be visible when treeSeqOutput() is called (although these will not be SLiM populations); if, in this example, we had used Dictionary("p0", 0, "p1", 1, "p5", 2) and then we wrote the result out with treeSeqOutput(), the resulting tree sequence would have six populations, although three of them would be empty and would not be used by SLiM.  The use of subpopMap makes it easier to load simulation data that was generated in Python, since that typically uses an id of 0.  The subpopMap parameter may not be used with file formats other than tree-sequence files, at the present time; setting up the correct subpopulation ids is typically easier when working with those other formats.  Note the tskit command-line interface can be used, like python3 -m tskit populations file.trees, to find out the number of subpopulations in a tree-sequence file and their IDs.

When loading a tree sequence, a crosscheck of the loaded data will be performed to ensure that the tree sequence was well-formed and was loaded correctly.  When running a Release build of SLiM, however, this crosscheck will only occur the first time that readFromPopulationFile() is called to load a tree sequence; subsequent calls will not perform this crosscheck, for greater speed when running models that load saved population state many times (such as models that are conditional on fixation).  If you suspect that a tree sequence file might be corrupted, or might be read incorrectly, running a Debug build of SLiM enables crosschecks after every load.

-

– (void)recalculateFitness([Ni$ tick = NULL])

+

– (void)recalculateFitness([Ni$ tick = NULL], [logical$ forceRecalc = T])

Force an immediate recalculation of fitness values for all individuals in all subpopulations.  Normally fitness values are calculated at a fixed point in each tick, and those values are cached and used until the next recalculation.  If simulation parameters are changed in script in a way that affects fitness calculations, and if you wish those changes to take effect immediately rather than taking effect at the next automatic recalculation, you may call recalculateFitness() to force an immediate recalculation and recache.

The optional parameter tick provides the tick for which mutationEffect() and fitnessEffect() callbacks should be selected; if it is NULL (the default), the current tick value for the simulation, community.tick, is used.  If you call recalculateFitness() in an early() event in a WF model, you may wish this to be community.tick - 1 in order to utilize the mutationEffect() and fitnessEffect() callbacks for the previous tick, as if the changes that you have made to fitness-influencing parameters were already in effect at the end of the previous tick when the new generation was first created and evaluated (usually it is simpler to just make such changes in a late() event instead, however, in which case calling recalculateFitness() is probably not necessary at all since fitness values will be recalculated immediately afterwards).  Regardless of the value supplied for tick here, community.tick inside callbacks will report the true tick number, so if your callbacks consult that parameter in order to create tick-specific fitness effects you will need to handle the discrepancy somehow.  (Similar considerations apply for nonWF models that call recalculateFitness() in a late() event, which is also not advisable in general.)

+

If forceRecalc is T (the default), this method will force the recalculation of all trait values upon which fitness depends.  (If a trait does not have a direct effect on fitness, it will not be recalculated even when forceRecalc is T; use demandPhenotype() to force recalculation of such traits.)  If forceRecalc is F, trait values will only be recalculated if they are marked as invalid – in other words, if their value is NAN.  This option can improve performance by skipping redundant calculations, but can produce out-of-date fitness values if something in the model state has changed such that trait values actually do need to be recalculated even though they are not NAN.  This could occur if, for example, if a mutationEffect() callback’s activation state has been changed.

After this call, the fitness values used for all purposes in SLiM will be the newly calculated values.  Calling this method will trigger the calling of any enabled and applicable mutationEffect() and fitnessEffect() callbacks, so this is quite a heavyweight operation; you should think carefully about what side effects might result (which is why fitness recalculation does not just occur automatically after changes that might affect fitness values).

– (object<SLiMEidosBlock>$)registerFitnessEffectCallback(Nis$ id, string$ source, [Nio<Subpopulation>$ subpop = NULL], [Ni$ start = NULL], [Ni$ end = NULL])

Register a block of Eidos source code, represented as the string singleton source, as an Eidos fitnessEffect() callback in the current simulation (specific to the target species), with an optional subpopulation subpop (which may be an integer identifier, or NULL, the default, to indicate all subpopulations), and optional start and end ticks all limiting its applicability.  The script block will be given identifier id (specified as an integer, or as a string symbolic name such as "s5"); this may be NULL if there is no need to be able to refer to the block later.  The registered callback is added to the end of the list of registered SLiMEidosBlock objects, and is active immediately; it may be eligible to execute in the current tick.  The new SLiMEidosBlock will be defined as a global variable immediately by this method, and will also be returned by this method.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 535e8d9f..ae0b547c 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -10487,7 +10487,7 @@ When loading a tree sequence, a crosscheck of the loaded data will be performed \f4\fs20 is called to load a tree sequence; subsequent calls will not perform this crosscheck, for greater speed when running models that load saved population state many times (such as models that are conditional on fixation). If you suspect that a tree sequence file might be corrupted, or might be read incorrectly, running a Debug build of SLiM enables crosschecks after every load.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 \'96\'a0(void)recalculateFitness([Ni$\'a0tick\'a0=\'a0NULL])\ +\f3\fs18 \cf0 \'96\'a0(void)recalculateFitness([Ni$\'a0tick\'a0=\'a0NULL]\cf2 , [logical$\'a0forceRecalc\'a0=\'a0T]\cf0 )\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Force an immediate recalculation of fitness values for all individuals in all subpopulations. Normally fitness values are calculated at a fixed point in each tick, and those values are cached and used until the next recalculation. If simulation parameters are changed in script in a way that affects fitness calculations, and if you wish those changes to take effect immediately rather than taking effect at the next automatic recalculation, you may call @@ -10526,7 +10526,30 @@ The optional parameter \f4\fs20 in a \f3\fs18 late() \f4\fs20 event, which is also not advisable in general.)\ -After this call, the fitness values used for all purposes in SLiM will be the newly calculated values. Calling this method will trigger the calling of any enabled and applicable +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 If +\f3\fs18 forceRecalc +\f4\fs20 is +\f3\fs18 T +\f4\fs20 (the default), this method will force the recalculation of all trait values upon which fitness depends. (If a trait does not have a direct effect on fitness, it will not be recalculated even when +\f3\fs18 forceRecalc +\f4\fs20 is +\f3\fs18 T +\f4\fs20 ; use +\f3\fs18 demandPhenotype() +\f4\fs20 to force recalculation of such traits.) If +\f3\fs18 forceRecalc +\f4\fs20 is +\f3\fs18 F +\f4\fs20 , trait values will only be recalculated if they are marked as invalid \'96 in other words, if their value is +\f3\fs18 NAN +\f4\fs20 . This option can improve performance by skipping redundant calculations, but can produce out-of-date fitness values if something in the model state has changed such that trait values actually do need to be recalculated even though they are not +\f3\fs18 NAN +\f4\fs20 . This could occur if, for example, if a +\f3\fs18 mutationEffect() +\f4\fs20 callback\'92s activation state has been changed.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 After this call, the fitness values used for all purposes in SLiM will be the newly calculated values. Calling this method will trigger the calling of any enabled and applicable \f3\fs18 mutationEffect() \f4\fs20 and \f3\fs18 fitnessEffect() diff --git a/VERSIONS b/VERSIONS index a9c33337..739df553 100644 --- a/VERSIONS +++ b/VERSIONS @@ -123,6 +123,7 @@ multitrait branch: note that there is no concept of independent dominance for the hemizygous dominance coefficient, since hemizygous mutations are present in only one copy by definition switch fitness calculation over to being based upon the calculated values of traits, rather than directly upon mutations eliminate overhead for setting up fitness buffers in neutral WF models; this should provide a significant speedup for such models, if they don't use a mateChoice() callback + extend recalculateFitness() with [logical$ forceRecalc = T] option for backward compatibility, but allowing trait value recalculation not to be forced version 5.1 (Eidos version 4.1): diff --git a/core/community.cpp b/core/community.cpp index a1843107..c6a5ebbb 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -2894,7 +2894,7 @@ bool Community::_RunOneTickWF(void) if (is_explicit_species_) gSLiMScheduling << "\tfitness recalculation: species " << species->name_ << std::endl; #endif - species->RecalculateFitness(); + species->RecalculateFitness(/* p_force_trait_recalculation */ false); executing_species_ = nullptr; } @@ -3197,7 +3197,7 @@ bool Community::_RunOneTickNonWF(void) if (is_explicit_species_) gSLiMScheduling << "\tfitness recalculation: species " << species->name_ << std::endl; #endif - species->RecalculateFitness(); + species->RecalculateFitness(/* p_force_trait_recalculation */ false); executing_species_ = nullptr; } diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 640991eb..e21536d3 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2858,7 +2858,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // for the singleton case for each of the parameters, get all the info MutationType *singleton_mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(arg_muttype, 0, &community, species, method_name.c_str()); // SPECIES CONSISTENCY CHECK - slim_effect_t singleton_selection_coeff = (arg_effect ? (slim_effect_t)arg_effect->NumericAtIndex_NOCAST(0, nullptr) : 0.0); + slim_effect_t singleton_selection_coeff = (arg_effect ? (slim_effect_t)arg_effect->NumericAtIndex_NOCAST(0, nullptr) : (slim_effect_t)0.0); slim_position_t singleton_position = SLiMCastToPositionTypeOrRaise(arg_position->IntAtIndex_NOCAST(0, nullptr)); diff --git a/core/mutation.cpp b/core/mutation.cpp index 9417393d..4401b62a 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -78,8 +78,8 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // FIXME MULTITRAIT: This constructor needs to change to have a whole vector of trait information passed in, for effect and dominance // for now we use the values passed in for trait 0, and make other traits neutral - slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : 0.0; - slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : 0.5; // can be NAN + slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : (slim_effect_t)0.0; + slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : (slim_effect_t)0.5; // can be NAN slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably traitInfoRec->effect_size_ = effect; @@ -305,8 +305,8 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // FIXME MULTITRAIT: This constructor needs to change to have a whole vector of trait information passed in, for effect and dominance // for now we use the values passed in for trait 0, and make other traits neutral - slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : 0.0; - slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : 0.5; // can be NAN + slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : (slim_effect_t)0.0; + slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : (slim_effect_t)0.5; // can be NAN slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably traitInfoRec->effect_size_ = effect; diff --git a/core/population.cpp b/core/population.cpp index 6128e116..8bd23188 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5241,7 +5241,7 @@ void Population::AddTallyForMutationTypeAndBinNumber(int p_mutation_type_index, } #endif -void Population::RecalculateFitness(slim_tick_t p_tick) +void Population::RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recalculation) { // calculate the fitnesses of the parents and make lookup tables; the main thing we do here is manage the mutationEffect() callbacks // as per the SLiM design spec, we get the list of callbacks once, and use that list throughout this stage, but we construct @@ -5438,7 +5438,7 @@ void Population::RecalculateFitness(slim_tick_t p_tick) std::vector no_callbacks; for (std::pair &subpop_pair : subpops_) - subpop_pair.second->UpdateFitness(no_callbacks, no_callbacks, p_direct_effect_trait_indices); + subpop_pair.second->UpdateFitness(no_callbacks, no_callbacks, p_direct_effect_trait_indices, p_force_trait_recalculation); } else { @@ -5466,7 +5466,7 @@ void Population::RecalculateFitness(slim_tick_t p_tick) } // Update fitness values, using the callbacks - subpop->UpdateFitness(subpop_mutationEffect_callbacks, subpop_fitnessEffect_callbacks, p_direct_effect_trait_indices); + subpop->UpdateFitness(subpop_mutationEffect_callbacks, subpop_fitnessEffect_callbacks, p_direct_effect_trait_indices, p_force_trait_recalculation); } } diff --git a/core/population.h b/core/population.h index 581139ad..64c49a18 100644 --- a/core/population.h +++ b/core/population.h @@ -221,7 +221,7 @@ class Population Individual *(Subpopulation::*GenerateIndividualCloned_TEMPLATED)(Individual *p_parent) = nullptr; // Recalculate all fitness values for the parental generation, including the use of mutationEffect() callbacks - void RecalculateFitness(slim_tick_t p_tick); + void RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recalculation); // Scan through all mutation runs in the simulation and unique them void UniqueMutationRuns(void); diff --git a/core/species.cpp b/core/species.cpp index c66010a1..352d469c 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -2550,7 +2550,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid std::vector mutationEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, subpop_id, -1, -1); std::vector fitnessEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, subpop_id, -1, -1); - subpop->UpdateFitness(mutationEffect_callbacks, fitnessEffect_callbacks, p_direct_effect_trait_indices); + subpop->UpdateFitness(mutationEffect_callbacks, fitnessEffect_callbacks, p_direct_effect_trait_indices, /* p_force_trait_recalculation */ true); } community_.executing_block_type_ = old_executing_block_type; @@ -2990,9 +2990,9 @@ void Species::MaintainMutationRegistry(void) } } -void Species::RecalculateFitness(void) +void Species::RecalculateFitness(bool p_force_trait_recalculation) { - population_.RecalculateFitness(cycle_); // used to be cycle_ + 1 in the WF cycle; removing that 18 Feb 2016 BCH + population_.RecalculateFitness(cycle_, p_force_trait_recalculation); // used to be cycle_ + 1 in the WF cycle; removing that 18 Feb 2016 BCH } void Species::MaintainTreeSequence(void) diff --git a/core/species.h b/core/species.h index 6e15bb58..954ed278 100644 --- a/core/species.h +++ b/core/species.h @@ -478,7 +478,7 @@ class Species : public EidosDictionaryUnretained bool HasDoneAnyInitialization(void); void PrepareForCycle(void); void MaintainMutationRegistry(void); - void RecalculateFitness(void); + void RecalculateFitness(bool p_force_trait_recalculation); void MaintainTreeSequence(void); void EmptyGraveyard(void); void FinishMutationRunExperimentTimings(void); diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index eee514aa..48d9db1c 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -3981,7 +3981,7 @@ EidosValue_SP Species::ExecuteMethod_readFromPopulationFile(EidosGlobalStringID return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(file_tick)); } -// ********************* – (void)recalculateFitness([Ni$ tick = NULL]) +// ********************* – (void)recalculateFitness([Ni$ tick = NULL], [l$ forceRecalc = T]) // EidosValue_SP Species::ExecuteMethod_recalculateFitness(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -4000,13 +4000,19 @@ EidosValue_SP Species::ExecuteMethod_recalculateFitness(EidosGlobalStringID p_me EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_recalculateFitness): recalculateFitness() may not be called from inside a callback." << EidosTerminate(); EidosValue *tick_value = p_arguments[0].get(); + slim_tick_t tick = (tick_value->Type() != EidosValueType::kValueNULL) ? SLiMCastToTickTypeOrRaise(tick_value->IntAtIndex_NOCAST(0, nullptr)) : community_.Tick(); + + // BCH 12/31/2025: Before multitrait this method would recalculate all fitness values directly from the mutations in haplosomes. + // We want to preserve that behavior for backward compatibility, so we need to tell RecalculateFitness() to recalculate with + // f_force_recalc turned on when demanding phenotypes. However, that is of course slow/wasteful if the trait values are already + // correct; maybe all the user has changed is, say, disabling a mutationEffect() callback. I am therefore adding a new option. + EidosValue *forceRecalc_value = p_arguments[1].get(); + eidos_logical_t forceRecalc = forceRecalc_value->LogicalAtIndex_NOCAST(0, nullptr); // Trigger a fitness recalculation. This is suggested after making a change that would modify fitness values, such as altering // a selection coefficient or dominance coefficient, changing the mutation type for a mutation, etc. It will have the side // effect of calling mutationEffect() callbacks, so this is quite a heavyweight operation. - slim_tick_t tick = (tick_value->Type() != EidosValueType::kValueNULL) ? SLiMCastToTickTypeOrRaise(tick_value->IntAtIndex_NOCAST(0, nullptr)) : community_.Tick(); - - population_.RecalculateFitness(tick); + population_.RecalculateFitness(tick, forceRecalc); // Remember that we have recalculated fitness values; this unlocks the ability to call cachedFitness(), temporarily has_recalculated_fitness_ = true; @@ -4764,7 +4770,7 @@ const std::vector *Species_Class::Methods(void) const methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_outputFull, kEidosValueMaskVOID))->AddString_OSN(gEidosStr_filePath, gStaticEidosValueNULL)->AddLogical_OS("binary", gStaticEidosValue_LogicalF)->AddLogical_OS("append", gStaticEidosValue_LogicalF)->AddLogical_OS("spatialPositions", gStaticEidosValue_LogicalT)->AddLogical_OS("ages", gStaticEidosValue_LogicalT)->AddLogical_OS("ancestralNucleotides", gStaticEidosValue_LogicalT)->AddLogical_OS("pedigreeIDs", gStaticEidosValue_LogicalF)->AddLogical_OS("objectTags", gStaticEidosValue_LogicalF)->AddLogical_OS("substitutions", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_outputMutations, kEidosValueMaskVOID))->AddObject("mutations", gSLiM_Mutation_Class)->AddString_OSN(gEidosStr_filePath, gStaticEidosValueNULL)->AddLogical_OS("append", gStaticEidosValue_LogicalF)->AddLogical_OS("objectTags", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_readFromPopulationFile, kEidosValueMaskInt | kEidosValueMaskSingleton))->AddString_S(gEidosStr_filePath)->AddObject_OSN("subpopMap", gEidosDictionaryUnretained_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_recalculateFitness, kEidosValueMaskVOID))->AddInt_OSN("tick", gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_recalculateFitness, kEidosValueMaskVOID))->AddInt_OSN("tick", gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalT)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_registerFitnessEffectCallback, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SLiMEidosBlock_Class))->AddIntString_SN("id")->AddString_S(gEidosStr_source)->AddIntObject_OSN("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddInt_OSN("start", gStaticEidosValueNULL)->AddInt_OSN("end", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_registerMateChoiceCallback, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SLiMEidosBlock_Class))->AddIntString_SN("id")->AddString_S(gEidosStr_source)->AddIntObject_OSN("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddInt_OSN("start", gStaticEidosValueNULL)->AddInt_OSN("end", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_registerModifyChildCallback, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SLiMEidosBlock_Class))->AddIntString_SN("id")->AddString_S(gEidosStr_source)->AddIntObject_OSN("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddInt_OSN("start", gStaticEidosValueNULL)->AddInt_OSN("end", gStaticEidosValueNULL)); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index e7de650b..854a53a7 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1356,7 +1356,7 @@ void Subpopulation::FixNonNeutralCaches_OMP(void) // calls UpdateFitness() on each subpopulation. This method expresses demand for the traits in question, and // then produces fitness values by factoring in fitnessEffect() callbacks and fitnessScaling values. It stores // the fitness values in the appropriate places to prepare for their later use. -void Subpopulation::UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) +void Subpopulation::UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation) { // Determine whether we are in a "pure neutral" case where we don't need to calculate individual fitness // because all individuals have neutral fitness. The simplest case where this is true is if there are no @@ -1424,9 +1424,14 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect // demand phenotypes for all the relevant traits if (p_direct_effect_trait_indices.size()) + { #warning make a new DemandPhenotype() function for whole subpops #warning need to think about shuffling the order for DemandPhenotype as well! - gSLiM_Individual_Class->DemandPhenotype(&species_, parent_individuals_.data(), (int)parent_individuals_.size(), p_direct_effect_trait_indices); // FIXME MULTITRAIT: pass in p_mutationEffect_callbacks to a per-subpop version of this + if (p_force_trait_recalculation) + gSLiM_Individual_Class->DemandPhenotype(&species_, parent_individuals_.data(), (int)parent_individuals_.size(), p_direct_effect_trait_indices); // FIXME MULTITRAIT: pass in p_mutationEffect_callbacks to a per-subpop version of this + else + gSLiM_Individual_Class->DemandPhenotype(&species_, parent_individuals_.data(), (int)parent_individuals_.size(), p_direct_effect_trait_indices); // FIXME MULTITRAIT: pass in p_mutationEffect_callbacks to a per-subpop version of this + } // then loop over individuals and pull together the relevant phenotype values, fitnessEffect() callbacks, // subpopulation fitnessScaling, and individual fitnessScaling to produce final individual fitness values; diff --git a/core/subpopulation.h b/core/subpopulation.h index 23cc5c63..a13c824c 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -381,7 +381,7 @@ class Subpopulation : public EidosDictionaryUnretained void FixNonNeutralCaches_OMP(void); #endif - void UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); + void UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation); template void _UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); diff --git a/eidos/eidos_simd.h b/eidos/eidos_simd.h index b078cd45..cd75432e 100644 --- a/eidos/eidos_simd.h +++ b/eidos/eidos_simd.h @@ -56,8 +56,17 @@ // Include SLEEF for vectorized transcendental functions (exp, log, log10, log2) // SLEEF is only used when AVX2+FMA or NEON is available +// BCH 12/31/2025: SLEEF generates tons of shadowed variable warnings for some reason; disable them #if defined(EIDOS_HAS_AVX2) || defined(EIDOS_HAS_NEON) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wdouble-promotion" #include "sleef/sleef_config.h" +#pragma clang diagnostic pop +#pragma GCC diagnostic pop #endif // ================================ From 843aa83586264bd992323b6642d91ffd3c6f63dc Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 1 Jan 2026 13:46:16 -0600 Subject: [PATCH 051/107] redesign mateChoice() callback internals for speed --- VERSIONS | 1 + core/population.cpp | 207 ++++++++++++++++++------------- core/slim_test.cpp | 1 + core/slim_test.h | 1 + core/slim_test_core.cpp | 269 ++++++++++++++++++++++++++++++++++++++++ core/species.cpp | 4 +- core/subpopulation.cpp | 45 ++++--- core/subpopulation.h | 10 +- eidos/eidos_value.h | 8 ++ 9 files changed, 429 insertions(+), 117 deletions(-) diff --git a/VERSIONS b/VERSIONS index 739df553..6085ffd3 100644 --- a/VERSIONS +++ b/VERSIONS @@ -124,6 +124,7 @@ multitrait branch: switch fitness calculation over to being based upon the calculated values of traits, rather than directly upon mutations eliminate overhead for setting up fitness buffers in neutral WF models; this should provide a significant speedup for such models, if they don't use a mateChoice() callback extend recalculateFitness() with [logical$ forceRecalc = T] option for backward compatibility, but allowing trait value recalculation not to be forced + redesigned the internals of mateChoice() callbacks; there should be no user-visible consequence apart from better performance (even without mateChoice() callbacks!) version 5.1 (Eidos version 4.1): diff --git a/core/population.cpp b/core/population.cpp index 8bd23188..65f31dab 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -712,18 +712,21 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMateChoiceCallback; bool sex_enabled = p_subpop->sex_enabled_; - double *standard_weights = (sex_enabled ? p_source_subpop->cached_male_fitness_ : p_source_subpop->cached_parental_fitness_); - Individual *chosen_mate = nullptr; // callbacks can return an Individual instead of a weights vector, held here - bool weights_reflect_chosen_mate = false; // if T, a weights vector has been created with a 1 for the chosen mate, to pass to the next callback SLiMEidosBlock *last_interventionist_mate_choice_callback = nullptr; - // We start out using standard weights taken from the source subpopulation. NOTE THAT THOSE COULD BE NULLPTR, in the case where - // the fitness of all individuals is equal; if we need to, we will allocate the buffer in the source subpopulation ourselves. - // If, when we are done handling callbacks, we are still using the standard weights, then we can do a draw using our fast lookup - // tables (or equally weighted, if the weights are still nullptr). Otherwise, we will do a draw the hard way. - double *current_weights = standard_weights; - slim_popsize_t weights_length = p_source_subpop->cached_fitness_size_; - bool weights_modified = false; + // The way we handle mating weights shifted substantially after SLiM 5.1. The Subpopulation variable mate_choice_weights_ + // is our private scratch space for a vector of mating weights based upon the current subpopulation fitness values. + // We create it lazily only if we need it, which happens if a callback's code references the pseudo-parameter `weights`; + // otherwise we don't use it. If a callback returns a chosen mate, we track that with chosen_mate; if a callback returns + // a weights vector, we keep that in returned_weights. We do not modify mate_choice_weights_ once we set it up, and we + // designate it as a constant inside the callback so that the callback code can't mess with it. (That might cause a break + // in backward compatibility for models that used to modify and return it, but it's a significant performance win to be + // able to reuse it.) If, when we are done handling callbacks, we do not have a chosen mate or returned weights, we can + // do a draw using the standard WF mechanism. If we got returned weights, we will do a draw the hard way. + EidosValue_Float_SP returned_weights; // a vector of weights returned or created, owned by us + Individual *chosen_mate = nullptr; // callbacks can return an Individual instead of a weights vector, held here + bool weights_reflect_chosen_mate = false; // if T, returned_weights represents chosen_mate, to pass to the next callback + slim_popsize_t weights_length = p_source_subpop->parent_subpop_size_; for (SLiMEidosBlock *mate_choice_callback : p_mate_choice_callbacks) { @@ -754,29 +757,33 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind } #endif - // local variables for the callback parameters that we might need to allocate here, and thus need to free below - EidosValue_SP local_weights_ptr; - bool redraw_mating = false; - + // If a previous callback said it wanted a specific individual to be the mate, and this callbacks wants to see + // a `weights` value, then we now need to make a weights vector to represent that to the callback's code if (chosen_mate && !weights_reflect_chosen_mate && mate_choice_callback->contains_weights_) { - // A previous callback said it wanted a specific individual to be the mate. We now need to make a weights vector - // to represent that, since we have another callback that wants an incoming weights vector. - if (!weights_modified) + // We would like to modify and use an existing returned_weights buffer if possible, to save an + // allocation. However, returned_weights can end up pointing to an EidosValue that is owned by + // the simulation; we can only modify it if that is not the case, otherwise we need a new one. + if (!returned_weights || (returned_weights->UseCount() > 1)) { - current_weights = (double *)malloc(sizeof(double) * weights_length); // allocate a new weights vector - if (!current_weights) + returned_weights = EidosValue_Float_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float()); + if (!returned_weights) EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - weights_modified = true; } - EIDOS_BZERO(current_weights, sizeof(double) * weights_length); - current_weights[chosen_mate->index_] = 1.0; + returned_weights->resize_no_initialize(weights_length); + double *weights = returned_weights->data_mutable(); + + EIDOS_BZERO(weights, sizeof(double) * weights_length); + weights[chosen_mate->index_] = 1.0; weights_reflect_chosen_mate = true; } // The callback is active, so we need to execute it; we start a block here to manage the lifetime of the symbol table + EidosValue_Float_SP local_weights; + bool redraw_mating = false; + { EidosSymbolTable callback_symbols(EidosSymbolTableType::kContextConstantsTable, &community_.SymbolTable()); EidosSymbolTable client_symbols(EidosSymbolTableType::kLocalVariablesTable, &callback_symbols); @@ -809,48 +816,85 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind if (mate_choice_callback->contains_weights_) { - // current_weights could be nullptr at this point, if standard_weights was nullptr on entry. - // In that case, we need to set up standard_weights and then point to it with current_weights. - if (!current_weights) + // we need an EidosValue for the `weights` pseudo-parameter; these are several ways to get it + if (returned_weights) { - standard_weights = (double *)malloc(sizeof(double) * p_source_subpop->parent_subpop_size_); // allocate a new weights vector - if (!standard_weights) - EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + // if we have weights returned from a previous callback, including weights constructed + // above to represent a single chosen mate with `weights_reflect_chosen_mate`, use those + local_weights = returned_weights; + } + else if (p_source_subpop->mate_choice_weights_ && p_source_subpop->mate_choice_weights_valid_) + { + // if we have already constructed a vector of fitness-based weights, use those + local_weights = p_source_subpop->mate_choice_weights_; - // Then fill the buffer with the appropriate values, knowing that the model is neutral. - // This code used to be in UpdateWFFitnessBuffers(); now we do it ourselves on demand. - // Note that we only generate the buffer we need -- weights for choosing a second parent. - if (sex_enabled) + weights_reflect_chosen_mate = false; + } + else + { + // otherwise, we need to construct a new vector of fitness-based weights; but if there + // is an existing allocated vector for this purpose, we want to reuse that allocation + if (p_source_subpop->mate_choice_weights_) { - for (slim_popsize_t female_index = 0; female_index < p_source_subpop->parent_first_male_index_; female_index++) - standard_weights[female_index] = 0; - for (slim_popsize_t male_index = p_source_subpop->parent_first_male_index_; male_index < p_source_subpop->parent_subpop_size_; male_index++) - standard_weights[male_index] = 1.0; + local_weights = p_source_subpop->mate_choice_weights_; } else { - for (slim_popsize_t i = 0; i < p_source_subpop->parent_subpop_size_; i++) - standard_weights[i] = 1.0; + local_weights.reset(new (gEidosValuePool->AllocateChunk()) EidosValue_Float()); + if (!local_weights) + EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); } - // We then give the allocated weights buffer to the subpopulation. We do not want this - // to be a private copy; we want to allocate this buffer just once per tick if possible. - if (sex_enabled) - p_source_subpop->cached_male_fitness_ = standard_weights; + local_weights->resize_no_initialize(weights_length); + + double *local_weights_data = local_weights->data_mutable(); + + if (p_source_subpop->individual_cached_fitness_OVERRIDE_) + { + // The whole subpop has the same fitness, so initialize from that, but with 0.0 for females + double fitness = p_source_subpop->individual_cached_fitness_OVERRIDE_value_; + + if (sex_enabled) + { + for (slim_popsize_t female_index = 0; female_index < p_source_subpop->parent_first_male_index_; female_index++) + local_weights_data[female_index] = 0; + for (slim_popsize_t male_index = p_source_subpop->parent_first_male_index_; male_index < weights_length; male_index++) + local_weights_data[male_index] = fitness; + } + else + { + for (slim_popsize_t individual_index = 0; individual_index < weights_length; individual_index++) + local_weights_data[individual_index] = fitness; + } + } else - p_source_subpop->cached_parental_fitness_ = standard_weights; + { + // No fitness override is in place, so use fitness values from the individuals + Individual **individuals_data = p_source_subpop->parent_individuals_.data(); + + if (sex_enabled) + { + for (slim_popsize_t female_index = 0; female_index < p_source_subpop->parent_first_male_index_; female_index++) + local_weights_data[female_index] = 0; + for (slim_popsize_t male_index = p_source_subpop->parent_first_male_index_; male_index < weights_length; male_index++) + local_weights_data[male_index] = individuals_data[male_index]->cached_fitness_UNSAFE_; + } + else + { + for (slim_popsize_t individual_index = 0; individual_index < weights_length; individual_index++) + local_weights_data[individual_index] = individuals_data[individual_index]->cached_fitness_UNSAFE_; + } + } - p_source_subpop->cached_fitness_size_ = p_source_subpop->parent_subpop_size_; - p_source_subpop->cached_fitness_capacity_ = p_source_subpop->parent_subpop_size_; + // We then give the allocated weights buffer to the subpopulation. We do not want this + // to be a private copy; we want to allocate this buffer just once per run if possible. + p_source_subpop->mate_choice_weights_ = local_weights; + p_source_subpop->mate_choice_weights_valid_ = true; - // Then we reference that buffer with current_weights just as if it had existed on entry. - current_weights = standard_weights; - weights_length = p_source_subpop->cached_fitness_size_; - weights_modified = false; + weights_reflect_chosen_mate = false; } - local_weights_ptr = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(current_weights, weights_length)); - callback_symbols.InitializeConstantSymbolEntry(gEidosID_weights, local_weights_ptr); + callback_symbols.InitializeConstantSymbolEntry(gEidosID_weights, local_weights); } try @@ -861,14 +905,20 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind EidosValueType result_type = result->Type(); if (result_type == EidosValueType::kValueVOID) + { EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): mateChoice() callbacks must explicitly return a value." << EidosTerminate(mate_choice_callback->identifier_token_); + } else if (result_type == EidosValueType::kValueNULL) { - // NULL indicates that the mateChoice() callback did not wish to alter the weights, so we do nothing + // NULL indicates that the mateChoice() callback did not wish to alter the weights, so we + // do nothing. We don't want to set returned_weights to anything, if it is presently + // nullptr, because returned_weights of nullptr means "use the default mating weights", + // and that's a case we can optimize below to use the default WF mate choice mechanism. } else if (result_type == EidosValueType::kValueObject) { - // A singleton vector of type Individual may be returned to choose a specific mate + // A singleton vector of type Individual may be returned to choose a specific mate. We + // want to remember that individual, not the equivalent vector of mating weights. if ((result->Count() == 1) && (((EidosValue_Object *)result)->Class() == gSLiM_Individual_Class)) { #if DEBUG @@ -878,6 +928,7 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind // unsafe cast for speed chosen_mate = (Individual *)((EidosValue_Object *)result)->data()[0]; #endif + // we can construct an equivalent vector of mating weights, but we do that lazily weights_reflect_chosen_mate = false; // remember this callback for error attribution below @@ -899,22 +950,16 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind } else if (result_count == weights_length) { + // a non-zero float vector must match in size, and provides a new set of weights // if we used to have a specific chosen mate, we don't any more chosen_mate = nullptr; weights_reflect_chosen_mate = false; - // a non-zero float vector must match the size of the source subpop, and provides a - // new set of weights for us to use; note current_weights could be nullptr here! - if (!weights_modified) - { - current_weights = (double *)malloc(sizeof(double) * weights_length); // allocate a new weights vector - if (!current_weights) - EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - weights_modified = true; - } - - // use FloatData() to get the values, copy them with memcpy() - memcpy(current_weights, result->FloatData(), sizeof(double) * weights_length); + // we simply take over the returned EidosValue, avoiding any copying of data + // this is a bit tricky, though; it could be a value that remains owned by the + // simulation, such as a global constant or variable, so we can only modify it + // downstream if it turns out that we are its only owner + returned_weights.reset((EidosValue_Float *)result_SP.get()); // remember this callback for error attribution below last_interventionist_mate_choice_callback = mate_choice_callback; @@ -938,9 +983,6 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind // If this callback told us not to generate the child, we do not call the rest of the callback chain; we're done if (redraw_mating) { - if (weights_modified) - free(current_weights); - community_.executing_block_type_ = old_executing_block_type; #if (SLIMPROFILING == 1) @@ -958,9 +1000,6 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind { slim_popsize_t drawn_parent = chosen_mate->index_; - if (weights_modified) - free(current_weights); - if (sex_enabled) { if (drawn_parent < p_source_subpop->parent_first_male_index_) @@ -978,16 +1017,18 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind } // If a callback supplied a different set of weights, we need to use those weights to draw a male parent - if (weights_modified) + if (returned_weights) { slim_popsize_t drawn_parent = -1; double weights_sum = 0; int positive_count = 0; // first we assess the weights vector: get its sum, bounds-check it, etc. + const double *returned_weights_data = returned_weights->data(); + for (slim_popsize_t weight_index = 0; weight_index < weights_length; ++weight_index) { - double x = current_weights[weight_index]; + double x = returned_weights_data[weight_index]; if (!std::isfinite(x)) EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): weight returned by mateChoice() callback is not finite." << EidosTerminate(last_interventionist_mate_choice_callback->identifier_token_); @@ -1010,9 +1051,6 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind // chain, whereas returning a vector of 0 values can be modified by a downstream mateChoice() callback. Usually that is // not an important distinction. Returning float(0) is faster in principle, but if one is already constructing a vector // of weights that can simply end up being all zero, then this path is much easier. BCH 5 March 2017 - //EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): weights returned by mateChoice() callback sum to 0.0 or less." << EidosTerminate(last_interventionist_mate_choice_callback->identifier_token_); - free(current_weights); - community_.executing_block_type_ = old_executing_block_type; #if (SLIMPROFILING == 1) @@ -1029,7 +1067,7 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind // there is only a single positive value, so the callback has chosen a parent for us; we just need to locate it // we could have noted it above, but I don't want to slow down that loop, since many positive weights is the likely case for (slim_popsize_t weight_index = 0; weight_index < weights_length; ++weight_index) - if (current_weights[weight_index] > 0.0) + if (returned_weights_data[weight_index] > 0.0) { drawn_parent = weight_index; break; @@ -1044,7 +1082,7 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind for (slim_popsize_t weight_index = 0; weight_index < weights_length; ++weight_index) { - double weight = current_weights[weight_index]; + double weight = returned_weights_data[weight_index]; if (weight > 0.0) { @@ -1067,7 +1105,7 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind for (slim_popsize_t weight_index = 0; weight_index < weights_length; ++weight_index) { - bachelor_sum += current_weights[weight_index]; + bachelor_sum += returned_weights_data[weight_index]; if (the_rose_in_the_teeth <= bachelor_sum) { @@ -1079,15 +1117,10 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind // we should always have a chosen parent at this point if (drawn_parent == -1) - EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): failed to choose a mate." << EidosTerminate(last_interventionist_mate_choice_callback->identifier_token_); + EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): (internal error) failed to choose a mate." << EidosTerminate(last_interventionist_mate_choice_callback->identifier_token_); - free(current_weights); - - if (sex_enabled) - { - if (drawn_parent < p_source_subpop->parent_first_male_index_) - EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): second parent chosen by mateChoice() callback is female." << EidosTerminate(last_interventionist_mate_choice_callback->identifier_token_); - } + if ((sex_enabled) && (drawn_parent < p_source_subpop->parent_first_male_index_)) + EIDOS_TERMINATION << "ERROR (Population::ApplyMateChoiceCallbacks): second parent chosen by mateChoice() callback is female." << EidosTerminate(last_interventionist_mate_choice_callback->identifier_token_); community_.executing_block_type_ = old_executing_block_type; diff --git a/core/slim_test.cpp b/core/slim_test.cpp index 8264b4af..d7fa2f3b 100644 --- a/core/slim_test.cpp +++ b/core/slim_test.cpp @@ -435,6 +435,7 @@ int RunSLiMTests(void) _RunIndividualTests(); _RunSubstitutionTests(); _RunSLiMEidosBlockTests(); + _RunMateChoiceTests(); _RunContinuousSpaceTests(); _RunSpatialMapTests(); _RunNonWFTests(); diff --git a/core/slim_test.h b/core/slim_test.h index fb9901e8..397e5d53 100644 --- a/core/slim_test.h +++ b/core/slim_test.h @@ -57,6 +57,7 @@ extern void _RunRelatednessTests(void); extern void _RunInteractionTypeTests(void); extern void _RunSubstitutionTests(void); extern void _RunSLiMEidosBlockTests(void); +extern void _RunMateChoiceTests(void); extern void _RunContinuousSpaceTests(void); extern void _RunSpatialMapTests(void); extern void _RunNonWFTests(void); diff --git a/core/slim_test_core.cpp b/core/slim_test_core.cpp index dedb9459..a05a4a6b 100644 --- a/core/slim_test_core.cpp +++ b/core/slim_test_core.cpp @@ -2664,6 +2664,275 @@ void _RunSLiMEidosBlockTests(void) SLiMAssertScriptRaise(tickexpr9, "unrecognized function name tuck", __LINE__); } +#pragma mark mateChoice() callback tests +void _RunMateChoiceTests(void) +{ + // With the multitrait work, I completely redesigned how mateChoice() callbacks work under the hood. They + // should be much faster, but their logic is a bit tricky, so I'm adding a raft of new tests. + + // This script tags everybody, and marks one individual as the preferred mate. It then confirms that that + // mate was chosen after mating has completed. This is the basis of all the mateChoice() tests here. + std::string verifiableMating1(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + } + mateChoice() { + return p1.subsetIndividuals(tag=1); + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating1); + + // add a no-op mateChoice() callback before the main one + std::string verifiableMating2(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + } + mateChoice() { + return NULL; + } + mateChoice() { + return p1.subsetIndividuals(tag=1); + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating2); + + // choose a random mate, and then change our minds + std::string verifiableMating3(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + } + mateChoice() { + return p1.sampleIndividuals(1); + } + mateChoice() { + return p1.subsetIndividuals(tag=1); + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating3); + + // return a random weights vector, and then change our minds + std::string verifiableMating4(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + } + mateChoice() { + return runif(subpop.individualCount); + } + mateChoice() { + return p1.subsetIndividuals(tag=1); + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating4); + + // do some random action, and then change our minds + std::string verifiableMating5(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + } + mateChoice() { + if (runif(1) < 0.3) + return float(0); + if (runif(1) < 0.3) + return p1.sampleIndividuals(1); + if (runif(1) < 0.3) + return runif(subpop.individualCount); + if (runif(1) < 0.3) + return weights; + return NULL; + } + mateChoice() { + return p1.subsetIndividuals(tag=1); + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating5); + + // choose the right individual, then return a weights vector representing that individual + std::string verifiableMating6(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + } + mateChoice() { + return p1.subsetIndividuals(tag=1); + } + mateChoice() { + return weights; + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating6); + + // choose the right individual, then return a weights vector representing that individual + std::string verifiableMating7(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + } + mateChoice() { + return p1.subsetIndividuals(tag=1); + } + mateChoice() { + return weights * runif(subpop.individualCount); + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { if (any(p1.individuals.tag != 1)) stop(); } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating7); + + // add a bit of stochasticity to the previous, so that the fly individual isn't always chosen + std::string verifiableMating8(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + } + mateChoice() { + return p1.subsetIndividuals(tag=1); + } + mateChoice() { + return weights + runif(subpop.individualCount, max=0.0001); + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { if (mean(p1.individuals.tag != 1) > 0.1) stop(); } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating8); + + // test using a global weights vector + std::string verifiableMating9(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + + defineGlobal("WEIGHTS", rep(0.0, p1.individualCount)); + WEIGHTS[whichMax(p1.individuals.tag)] = 1.0; + } + mateChoice() { + return runif(subpop.individualCount); + } + mateChoice() { + return WEIGHTS; + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { if (mean(p1.individuals.tag != 1) > 0.1) stop(); } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating9); + + // finally, try to trigger an illegal modification of the global weights vector + std::string verifiableMating10(R"V0G0N( + initialize() {} + 1 early() { + sim.addSubpop("p1", 50); + } + early() { + p1.individuals.tag = 0; + p1.sampleIndividuals(1).tag = 1; + + defineGlobal("WEIGHTS", rep(0.0, p1.individualCount)); + WEIGHTS[whichMax(p1.individuals.tag)] = 1.0; + defineGlobal("CHECK", WEIGHTS * 2.0); + } + mateChoice() { + // first return the global, which should get shared into returned_weights + return WEIGHTS; + } + mateChoice() { + // then return a chosen individual, which should set chosen_mate + return p1.subsetIndividuals(tag=1); + } + mateChoice() { + // then use weights, forcing a new build into returned_weights + return weights * 10.0; + } + modifyChild() { + child.tag = parent2.tag; + return T; + } + 1:100 late() { + if (!identical(WEIGHTS * 2.0, CHECK)) stop(); + if (mean(p1.individuals.tag != 1) > 0.1) stop(); + } + )V0G0N"); + SLiMAssertScriptSuccess(verifiableMating10); +} + diff --git a/core/species.cpp b/core/species.cpp index 352d469c..cc262422 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -4455,8 +4455,8 @@ void Species::TabulateSLiMMemoryUsage_Species(SLiMMemoryUsage_Species *p_usage) if (subpop.cached_parental_fitness_) p_usage->subpopulationFitnessCaches += subpop.cached_fitness_capacity_ * sizeof(double); - if (subpop.cached_male_fitness_) - p_usage->subpopulationFitnessCaches += subpop.cached_fitness_capacity_ * sizeof(double); + if (subpop.mate_choice_weights_) + p_usage->subpopulationFitnessCaches += subpop.mate_choice_weights_->Count() * sizeof(double); p_usage->subpopulationParentTables += subpop.MemoryUsageForParentTables(); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 854a53a7..c50b57b3 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1232,8 +1232,8 @@ Subpopulation::~Subpopulation(void) if (cached_parental_fitness_) free(cached_parental_fitness_); - if (cached_male_fitness_) - free(cached_male_fitness_); + if (mate_choice_weights_) + mate_choice_weights_.reset(); cached_fitness_size_ = 0; cached_fitness_capacity_ = 0; @@ -1612,19 +1612,27 @@ void Subpopulation::UpdateWFFitnessBuffers(void) // This is called only by UpdateFitness(), after the fitness of all individuals has been updated, and only in // WF models. It updates cached fitness buffers, and then generates GSL-based lookup tables for mate choice. + // Since fitness buffers are being updated, this is a logical place to mark mate_choice_weights_ as invalid. + // This will cause ApplyMateChoiceCallbacks() to recache the next time it needs a weights vector. We also + // resize the vector for good form (maybe triggers an error on misuse), but that doesn't change its capacity. + // We do not release or deallocate it; we want to stick with the same allocated buffer through the whole run. + // This is the reason for the existence of mate_choice_weights_valid_: to let us reuse mate_choice_weights_. + if (mate_choice_weights_) + { + mate_choice_weights_->resize_no_initialize(0); + mate_choice_weights_valid_ = false; + } + if (individual_cached_fitness_OVERRIDE_) { // This is the optimized case, where all individuals have the same fitness and it is cached at the subpop // level. When that is the case, we don't use the GSL discrete preproc stuff to choose mates proportional // to fitness; we choose mates randomly with equal probability instead. Given that, we don't need to set - // up the buffers (cached_parental_fitness_, etc.) either; they are only used to set up the GSL's discrete - // preproc machinery. So we can actually free those buffers to decrease memory footprint, in this path. + // up the cached_parental_fitness_ buffer either; it is only used to set up the GSL's discrete preproc + // machinery. So we can actually free that buffer to decrease memory footprint, in this code path. if (cached_parental_fitness_) free(cached_parental_fitness_); - if (cached_male_fitness_) - free(cached_male_fitness_); - cached_fitness_size_ = 0; cached_fitness_capacity_ = 0; @@ -1657,27 +1665,18 @@ void Subpopulation::UpdateWFFitnessBuffers(void) else { // This is the normal case, where cached_fitness_UNSAFE_ has cached fitness values for each individual. - // In this case we need to set up buffers to create the GSL discrete preproc structs for drawing parents. - // In this code path we also need to total up individual fitness values to check for numerical problems. + // In this case we need to set up a buffer to create the GSL discrete preproc structs for drawing parents. + // In this code path we also need to total up individual fitness values to check for numerical problems; + // it's a convenient and efficient place to do that since we're making a pass through the values anyway. - // Reallocate the fitness buffers to be large enough; note that we up-cast to double here for the GSL. - // Due to the shenanigans we pull in ApplyMateChoiceCallbacks(), we might have a non-zero capacity set - // and yet have an unallocated buffer (especially in the sex case, at least for now); so we check that. - if (!cached_parental_fitness_ || (sex_enabled_ && !cached_male_fitness_) || (cached_fitness_capacity_ < parent_subpop_size_)) + // Reallocate cached_parental_fitness_ to be large enough; note that we up-cast to double for the GSL. + if (!cached_parental_fitness_ || (cached_fitness_capacity_ < parent_subpop_size_)) { - // there might be an existing capacity but a missing buffer; use the existing value if it's bigger - cached_fitness_capacity_ = std::max(cached_fitness_capacity_, parent_subpop_size_); + cached_fitness_capacity_ = parent_subpop_size_; cached_parental_fitness_ = (double *)realloc(cached_parental_fitness_, sizeof(double) * parent_subpop_size_); if (!cached_parental_fitness_) EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateWFFitnessBuffers): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - - if (sex_enabled_) - { - cached_male_fitness_ = (double *)realloc(cached_male_fitness_, sizeof(double) * parent_subpop_size_); - if (!cached_male_fitness_) - EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateWFFitnessBuffers): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - } } if (sex_enabled_) @@ -1689,7 +1688,6 @@ void Subpopulation::UpdateWFFitnessBuffers(void) double fitness = (double)parent_individuals_[female_index]->cached_fitness_UNSAFE_; cached_parental_fitness_[female_index] = fitness; - cached_male_fitness_[female_index] = 0; totalFemaleFitness += fitness; } @@ -1698,7 +1696,6 @@ void Subpopulation::UpdateWFFitnessBuffers(void) double fitness = (double)parent_individuals_[male_index]->cached_fitness_UNSAFE_; cached_parental_fitness_[male_index] = fitness; - cached_male_fitness_[male_index] = fitness; totalMaleFitness += fitness; } diff --git a/core/subpopulation.h b/core/subpopulation.h index a13c824c..773aba0d 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -177,11 +177,13 @@ class Subpopulation : public EidosDictionaryUnretained // These fitness cache buffers are additional to that, used only in WF models. They are now used for only one thing: as the data source for setting up our lookup // objects for drawing mates by fitness; the GSL wants that data to be in the form of a single buffer. In nonWF models these buffers are not used, and not even set // up. BCH 12/30/2025: up through SLiM 5.1 these buffers were also maintained for the use of mateChoice() callbacks in ApplyMateChoiceCallbacks(), as the data - // source for the `weights` pseudo-parameter; after SLiM 5.1 that method allocates these buffers itself, lazily, if they are not already present. + // source for the `weights` pseudo-parameter; after SLiM 5.1 that method allocates mate_choice_weights_ itself, lazily, if it is not already present. double *cached_parental_fitness_ = nullptr; // OWNED POINTER: cached in UpdateWFFitnessBuffers() - double *cached_male_fitness_ = nullptr; // OWNED POINTER: SEX ONLY: same as cached_parental_fitness_ but with 0 for all females - slim_popsize_t cached_fitness_size_ = 0; // the size (number of entries used) of cached_parental_fitness_ and cached_male_fitness_ - slim_popsize_t cached_fitness_capacity_ = 0; // the capacity of the malloced buffers cached_parental_fitness_ and cached_male_fitness_ + slim_popsize_t cached_fitness_size_ = 0; // the size (number of entries used) of cached_parental_fitness_ + slim_popsize_t cached_fitness_capacity_ = 0; // the capacity of the malloced buffer cached_parental_fitness_ + + EidosValue_Float_SP mate_choice_weights_; // WF ONLY: a cache used only by ApplyMateChoiceCallbacks(), as the canonical fitness-based `weights` vector + bool mate_choice_weights_valid_ = false; // if true, mate_choice_weights_ corresponds to cached_parental_fitness_ (with females set to 0 in sexual models) // WF only: // Optimized fitness caching at the Individual level. Individual has an ivar named cached_fitness_UNSAFE_ that keeps a cached fitness value for each individual. diff --git a/eidos/eidos_value.h b/eidos/eidos_value.h index 5897fcfb..696cf3e0 100644 --- a/eidos/eidos_value.h +++ b/eidos/eidos_value.h @@ -251,6 +251,7 @@ class EidosValue inline __attribute__((always_inline)) bool IsIteratorVariable(void) const { return iterator_var_; } virtual int Count(void) const = 0; // the only real casualty of removing the singleton/vector distinction: this is now a virtual function + virtual int Capacity(void) const = 0; virtual const std::string &ElementType(void) const = 0; // the type of the elements contained by the vector void Print(std::ostream &p_ostream, const std::string &p_indent = std::string()) const; // standard printing; same as operator<< void PrintStructure(std::ostream &p_ostream, int max_values) const; // print structure; no newline @@ -427,6 +428,7 @@ class EidosValue_VOID final : public EidosValue inline virtual ~EidosValue_VOID(void) override { } virtual int Count(void) const override { return 0; } + virtual int Capacity(void) const override { return 0; } virtual const std::string &ElementType(void) const override; virtual void PrintValueAtIndex(const int p_idx, std::ostream &p_ostream) const override; virtual nlohmann::json JSONRepresentation(void) const override; @@ -463,6 +465,7 @@ class EidosValue_NULL final : public EidosValue inline virtual ~EidosValue_NULL(void) override { } virtual int Count(void) const override { return 0; } + virtual int Capacity(void) const override { return 0; } virtual const std::string &ElementType(void) const override; virtual void PrintValueAtIndex(const int p_idx, std::ostream &p_ostream) const override; virtual nlohmann::json JSONRepresentation(void) const override; @@ -513,6 +516,7 @@ class EidosValue_Logical final : public EidosValue inline virtual ~EidosValue_Logical(void) override { free(values_); } virtual int Count(void) const override { return (int)count_; } + virtual int Capacity(void) const override { return (int)capacity_; } virtual const std::string &ElementType(void) const override; virtual void PrintValueAtIndex(const int p_idx, std::ostream &p_ostream) const override; virtual nlohmann::json JSONRepresentation(void) const override; @@ -648,6 +652,7 @@ class EidosValue_String final : public EidosValue std::vector &StringVectorData(void) { WILL_MODIFY(this); UncacheScript(); return values_; } // to get the std::vector for direct modification virtual int Count(void) const override { return (int)values_.size(); } + virtual int Capacity(void) const override { return (int)values_.capacity(); } virtual const std::string &ElementType(void) const override; virtual EidosValue_SP NewMatchingType(void) const override; virtual void PrintValueAtIndex(const int p_idx, std::ostream &p_ostream) const override; @@ -713,6 +718,7 @@ class EidosValue_Int final : public EidosValue virtual int64_t *IntData_Mutable(void) override { WILL_MODIFY(this); return values_; } virtual int Count(void) const override { return (int)count_; } + virtual int Capacity(void) const override { return (int)capacity_; } virtual const std::string &ElementType(void) const override; virtual EidosValue_SP NewMatchingType(void) const override; virtual void PrintValueAtIndex(const int p_idx, std::ostream &p_ostream) const override; @@ -839,6 +845,7 @@ class EidosValue_Float final : public EidosValue virtual double *FloatData_Mutable(void) override { WILL_MODIFY(this); return values_; } virtual int Count(void) const override { return (int)count_; } + virtual int Capacity(void) const override { return (int)capacity_; } virtual const std::string &ElementType(void) const override; virtual EidosValue_SP NewMatchingType(void) const override; virtual void PrintValueAtIndex(const int p_idx, std::ostream &p_ostream) const override; @@ -1006,6 +1013,7 @@ class EidosValue_Object final : public EidosValue inline __attribute__((always_inline)) bool UsesRetainRelease(void) const { return class_uses_retain_release_; } virtual int Count(void) const override { return (int)count_; } + virtual int Capacity(void) const override { return (int)capacity_; } virtual const std::string &ElementType(void) const override; virtual EidosValue_SP NewMatchingType(void) const override; virtual void PrintValueAtIndex(const int p_idx, std::ostream &p_ostream) const override; From f749aef92c7c2866b41cb7e492b5998766be2733 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 1 Jan 2026 14:56:30 -0600 Subject: [PATCH 052/107] add slim_trait_index_t typedef --- VERSIONS | 1 + core/community.cpp | 4 +- core/community.h | 2 +- core/haplosome.cpp | 4 +- core/individual.cpp | 132 ++++++++++++++++++++-------------------- core/individual.h | 6 +- core/mutation.cpp | 96 ++++++++++++++--------------- core/mutation_block.cpp | 2 +- core/mutation_block.h | 4 +- core/mutation_type.cpp | 52 ++++++++-------- core/mutation_type.h | 6 +- core/polymorphism.cpp | 24 ++++---- core/population.cpp | 4 +- core/slim_eidos_block.h | 2 +- core/slim_globals.h | 1 + core/species.cpp | 20 +++--- core/species.h | 8 +-- core/subpopulation.cpp | 58 +++++++++--------- core/subpopulation.h | 10 +-- core/substitution.cpp | 55 ++++++++--------- core/trait.h | 6 +- 21 files changed, 247 insertions(+), 250 deletions(-) diff --git a/VERSIONS b/VERSIONS index 6085ffd3..8c4ae5d3 100644 --- a/VERSIONS +++ b/VERSIONS @@ -125,6 +125,7 @@ multitrait branch: eliminate overhead for setting up fitness buffers in neutral WF models; this should provide a significant speedup for such models, if they don't use a mateChoice() callback extend recalculateFitness() with [logical$ forceRecalc = T] option for backward compatibility, but allowing trait value recalculation not to be forced redesigned the internals of mateChoice() callbacks; there should be no user-visible consequence apart from better performance (even without mateChoice() callbacks!) + add new slim_trait_index_t typedef to refer to trait indices, cleaning up a bunch of code (no user-visible impact) version 5.1 (Eidos version 4.1): diff --git a/core/community.cpp b/core/community.cpp index c6a5ebbb..da6d297e 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -556,7 +556,7 @@ void Community::ValidateScriptBlockCaches(void) } } -std::vector Community::ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_objectid_t p_trait_index, int64_t p_chromosome_id, Species *p_species) +std::vector Community::ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id, Species *p_species) { if (!script_block_types_cached_) ValidateScriptBlockCaches(); @@ -641,7 +641,7 @@ std::vector Community::ScriptBlocksMatching(slim_tick_t p_tick, // check that the trait index matches, if requested if (p_trait_index != -1) { - slim_objectid_t trait_index = script_block->trait_index_; + slim_trait_index_t trait_index = script_block->trait_index_; if ((trait_index != -1) && (p_trait_index != trait_index)) continue; diff --git a/core/community.h b/core/community.h index 3cd99448..55b8311d 100644 --- a/core/community.h +++ b/core/community.h @@ -214,7 +214,7 @@ class Community : public EidosDictionaryUnretained // Managing script blocks; these two methods should be used as a matched pair, bracketing each cycle stage that calls out to script void ValidateScriptBlockCaches(void); - std::vector ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_objectid_t p_trait_index, int64_t p_chromosome_id, Species *p_species); + std::vector ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id, Species *p_species); std::vector &AllScriptBlocks(); std::vector AllScriptBlocksForSpecies(Species *p_species); void OptimizeScriptBlock(SLiMEidosBlock *p_script_block); diff --git a/core/haplosome.cpp b/core/haplosome.cpp index e21536d3..8dcd4c11 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -4391,7 +4391,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID // Construct a vector of mutations to remove that is sorted by position std::vector mutations_to_remove; Mutation * const *mutations_data = (Mutation * const *)mutations_value->ObjectData(); - int trait_count = species->TraitCount(); + slim_trait_index_t trait_count = species->TraitCount(); for (int value_index = 0; value_index < mutations_count; ++value_index) { @@ -4410,7 +4410,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID { MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if (mut_trait_info[trait_index].effect_size_ != (slim_effect_t)0.0) { diff --git a/core/individual.cpp b/core/individual.cpp index 740aa061..e0afc0e4 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -131,7 +131,7 @@ void Individual::_InitializePerTraitInformation(void) Species &species = subpopulation_->species_; const std::vector &traits = species.Traits(); - int trait_count = (int)traits.size(); + slim_trait_index_t trait_count = species.TraitCount(); if (trait_count == 1) { @@ -176,7 +176,7 @@ void Individual::_InitializePerTraitInformation(void) if (!trait_info_) trait_info_ = static_cast(malloc(trait_count * sizeof(IndividualTraitInfo))); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { trait_info_[trait_index].phenotype_ = std::numeric_limits::quiet_NaN(); // "uncalculated" trait_info_[trait_index].offset_ = traits[trait_index]->DrawIndividualOffset(); @@ -2527,7 +2527,7 @@ EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID if (species) { Trait *trait = species->TraitFromStringID(p_property_id); - int64_t trait_index = trait->Index(); + slim_trait_index_t trait_index = trait->Index(); for (size_t value_index = 0; value_index < p_values_size; ++value_index) { @@ -2543,7 +2543,7 @@ EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID { const Individual *value = individuals_buffer[value_index]; Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); - int64_t trait_index = trait->Index(); + slim_trait_index_t trait_index = trait->Index(); float_result->set_float_no_check((double)value->trait_info_[trait_index].phenotype_, value_index); } @@ -3097,7 +3097,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope if (species) { Trait *trait = species->TraitFromStringID(p_property_id); - int64_t trait_index = trait->Index(); + slim_trait_index_t trait_index = trait->Index(); if (p_source_size == 1) { @@ -3131,7 +3131,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope { const Individual *value = individuals_buffer[value_index]; Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); - int64_t trait_index = trait->Index(); + slim_trait_index_t trait_index = trait->Index(); value->trait_info_[trait_index].phenotype_ = source_value; } @@ -3142,7 +3142,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope { const Individual *value = individuals_buffer[value_index]; Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); - int64_t trait_index = trait->Index(); + slim_trait_index_t trait_index = trait->Index(); value->trait_info_[trait_index].phenotype_ = (slim_effect_t)source_data[value_index]; } @@ -3349,12 +3349,12 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met // get the trait indices, with bounds-checking Species &species = subpopulation_->species_; - std::vector trait_indices; + std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "offsetForTrait"); if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; slim_effect_t offset = trait_info_[trait_index].offset_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)offset)); @@ -3363,7 +3363,7 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { slim_effect_t offset = trait_info_[trait_index].offset_; @@ -3384,12 +3384,12 @@ EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_ // get the trait indices, with bounds-checking Species &species = subpopulation_->species_; - std::vector trait_indices; + std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "phenotypeForTrait"); if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; slim_effect_t phenotype = trait_info_[trait_index].phenotype_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)phenotype)); @@ -3398,7 +3398,7 @@ EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_ { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { slim_effect_t phenotype = trait_info_[trait_index].phenotype_; @@ -4331,14 +4331,14 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires that all individuals belong to the same species." << EidosTerminate(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setOffsetForTrait"); - int trait_count = (int)trait_indices.size(); + slim_trait_index_t trait_count = (slim_trait_index_t)trait_indices.size(); if (offset_value->Type() == EidosValueType::kValueNULL) { // pattern 1: drawing a default offset value for each trait in one or more individuals - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; @@ -4373,7 +4373,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin // pattern 2: setting a single offset value across one or more traits in one or more individuals slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(0, nullptr)); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; slim_effect_t offset_for_trait = offset; @@ -4391,7 +4391,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin // pattern 3: setting one offset value per trait, in one or more individuals int offset_index = 0; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(offset_index++, nullptr)); @@ -4420,7 +4420,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; Trait *trait = species->Traits()[trait_index]; if (trait->Type() == TraitType::kMultiplicative) @@ -4452,7 +4452,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { Individual *ind = individuals_buffer[individual_index]; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; slim_effect_t offset = static_cast(*(offsets_int++)); @@ -4474,7 +4474,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; Trait *trait = species->Traits()[trait_index]; if (trait->Type() == TraitType::kMultiplicative) @@ -4506,7 +4506,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { Individual *ind = individuals_buffer[individual_index]; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; slim_effect_t offset = static_cast(*(offsets_float++)); @@ -4550,9 +4550,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires that all individuals belong to the same species." << EidosTerminate(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setPhenotypeForTrait"); - int trait_count = (int)trait_indices.size(); + slim_trait_index_t trait_count = (slim_trait_index_t)trait_indices.size(); if (phenotype_count == 1) { @@ -4562,7 +4562,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = phenotype; @@ -4573,7 +4573,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt { Individual *ind = individuals_buffer[individual_index]; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) ind->trait_info_[trait_index].phenotype_ = phenotype; } } @@ -4583,7 +4583,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt // pattern 2: setting one phenotype value per trait, in one or more individuals int phenotype_index = 0; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { slim_effect_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(phenotype_index++, nullptr)); @@ -4607,7 +4607,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); @@ -4618,7 +4618,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt { Individual *ind = individuals_buffer[individual_index]; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) ind->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); } } @@ -4631,7 +4631,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_float++)); @@ -4642,7 +4642,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt { Individual *ind = individuals_buffer[individual_index]; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) ind->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_float++)); } } @@ -5905,9 +5905,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotype(EidosGlobalStringI EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_demandPhenotype): demandPhenotype() requires that all individuals belong to the same species." << EidosTerminate(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "demandPhenotype"); - int trait_count = (int)trait_indices.size(); + slim_trait_index_t trait_count = (slim_trait_index_t)trait_indices.size(); if (trait_count == 0) return gStaticEidosValue_Float_ZeroVec; @@ -5929,7 +5929,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotype(EidosGlobalStringI } template -void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const +void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const { // Given a vector of individuals that are all guaranteed to belong to the provided species, and a vector of // trait indices guaranteed to be of length 1 or longer, this method loops over the chromosomes of the @@ -5946,7 +5946,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual std::vector mutationEffect_callbacks = species->CallbackBlocksMatching(species->community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1); Population &population = species->population_; bool has_active_callbacks = false; - int trait_indices_count = (int)trait_indices.size(); + slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); for (SLiMEidosBlock *callback : mutationEffect_callbacks) { @@ -5966,7 +5966,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual if ((int)subpop->per_trait_subpop_caches_.size() != species->TraitCount()) EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ is not correctly sized." << EidosTerminate(); - for (int trait_index = 0; trait_index < species->TraitCount(); trait_index++) + for (slim_trait_index_t trait_index = 0; trait_index < species->TraitCount(); trait_index++) { Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; @@ -6001,7 +6001,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual // For each trait we keep a separate vector of callbacks that apply to that trait. for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { - int64_t trait_index = trait_indices[trait_indices_index]; + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; TraitType traitType = trait->Type(); @@ -6023,7 +6023,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual if ((callback_subpop_id == -1) || (callback_subpop_id == subpop->subpopulation_id_)) { // check if this callback applies to this trait - int64_t callback_trait_index = callback->trait_index_; + slim_trait_index_t callback_trait_index = callback->trait_index_; if ((callback_trait_index == -1) || (callback_trait_index == trait_index)) subpop_per_trait_mutationEffect_callbacks.emplace_back(callback); @@ -6089,7 +6089,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual // if we have no active callbacks at all, we know that that will remain true across the operation for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { - int64_t trait_index = trait_indices[trait_indices_index]; + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; TraitType traitType = trait->Type(); @@ -6141,7 +6141,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { - int64_t trait_index = trait_indices[trait_indices_index]; + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; TraitType traitType = trait->Type(); slim_effect_t trait_baseline_offset = trait->BaselineOffset(); @@ -6204,7 +6204,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual { for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { - int64_t trait_index = trait_indices[trait_indices_index]; + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { @@ -6305,7 +6305,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual { for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { - int64_t trait_index = trait_indices[trait_indices_index]; + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { @@ -6336,7 +6336,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual { for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { - int64_t trait_index = trait_indices[trait_indices_index]; + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { @@ -6369,7 +6369,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual // clear out each subpopulation's per-trait caches that we set up above; these are only for our private use for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { - int64_t trait_index = trait_indices[trait_indices_index]; + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; for (std::pair &subpop_pair : population.subpops_) { @@ -6384,15 +6384,15 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual } } -template void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; -template void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; +template void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; +template void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; // Low-level method to calculate a phenotype for one individual, for one haploid (or hemizygous) chromosome, // for one trait. This will put the result of the calculation into the individual's phenotype information. // This is called by Individual_Class::DemandPhenotype(), which loops over chromosomes, traits, and individuals. template -void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks) +void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) { #if DEBUG // This method assumes that haplosome is not a null haplosome; the caller needs to guarantee this @@ -6473,24 +6473,24 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos trait_info_[trait_index].phenotype_ = effect_accumulator; } -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); // Low-level method to calculate a phenotype for one individual, for one diploid chromosome, for one trait. // This will put the result of the calculation into the individual's phenotype information. This is called // by Individual_Class::DemandPhenotype(), which loops over chromosomes, traits, and individuals. template -void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks) +void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) { #if DEBUG // This method assumes that haplosome1 and haplosome2 are not null; the caller needs to guarantee this @@ -6835,12 +6835,12 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos trait_info_[trait_index].phenotype_ = effect_accumulator; } -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); diff --git a/core/individual.h b/core/individual.h index 385d1b70..03a71e0a 100644 --- a/core/individual.h +++ b/core/individual.h @@ -411,10 +411,10 @@ class Individual : public EidosDictionaryUnretained // accumulated into the trait value of the focal individual, which must be set up with an initial value // see also the method DemandPhenotype() in class Individual_Class, which calls these methods template - void _IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks); + void _IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); template - void _IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks); + void _IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); // for Subpopulation::ExecuteMethod_takeMigrants() friend Subpopulation; @@ -447,7 +447,7 @@ class Individual_Class : public EidosDictionaryUnretained_Class // if f_force_recalc is true all values are recalculated; if false, only NAN trait values are recalculated // see also the methods class _IncorporateEffects_X() methods in class Individual, called by this method template - void DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; + void DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; }; diff --git a/core/mutation.cpp b/core/mutation.cpp index 4401b62a..0cda73a3 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -59,7 +59,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ MutationIndex mutation_index = mutation_block->IndexInBlock(this); mutation_block->refcount_buffer_[mutation_index] = 0; - int trait_count = mutation_block->trait_count_; + slim_trait_index_t trait_count = mutation_block->trait_count_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since @@ -70,7 +70,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits is_independent_dominance_ = std::isnan(p_dominance_coeff); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; Trait *trait = traits[trait_index]; @@ -159,7 +159,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ MutationIndex mutation_index = mutation_block->IndexInBlock(this); mutation_block->refcount_buffer_[mutation_index] = 0; - int trait_count = mutation_block->trait_count_; + slim_trait_index_t trait_count = mutation_block->trait_count_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since @@ -174,7 +174,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ { // The DES of the mutation type is pure neutral, so we don't need to do any draws; we can short-circuit // most of the work here and just set up neutral effects for all of the traits. - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; Trait *trait = traits[trait_index]; @@ -203,7 +203,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // The DES of the mutation type is not pure neutral. Note that species.pure_neutral_ might still be true // at this point; the mutation type for this mutation might not be used by any genomic element type, // because we might be getting called by addNewDrawnMutation() for a type that is otherwise unused. - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; Trait *trait = traits[trait_index]; @@ -286,7 +286,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ MutationIndex mutation_index = mutation_block->IndexInBlock(this); mutation_block->refcount_buffer_[mutation_index] = 0; - int trait_count = mutation_block->trait_count_; + slim_trait_index_t trait_count = mutation_block->trait_count_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since @@ -297,7 +297,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits is_independent_dominance_ = std::isnan(p_dominance_coeff); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; Trait *trait = traits[trait_index]; @@ -384,10 +384,10 @@ void Mutation::SelfConsistencyCheck(const std::string &p_message_end) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) mut_trait_info is nullptr" << p_message_end << "." << EidosTerminate(); const std::vector &traits = species.Traits(); - int trait_count = (int)traits.size(); + slim_trait_index_t trait_count = species.TraitCount(); bool all_neutral_effects = true; - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { Trait *trait = traits[trait_index]; MutationTraitInfo &traitInfoRec = mut_trait_info[trait_index]; @@ -438,7 +438,7 @@ void Mutation::SelfConsistencyCheck(const std::string &p_message_end) slim_effect_t Mutation::RealizedDominanceForTrait(Trait *p_trait) { - int64_t trait_index = p_trait->Index(); + slim_trait_index_t trait_index = p_trait->Index(); Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); @@ -528,11 +528,11 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); - int trait_count = species.TraitCount(); + slim_trait_index_t trait_count = species.TraitCount(); is_neutral_ = true; - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if ((mut_trait_info + trait_index)->effect_size_ != (slim_effect_t)0.0) { @@ -690,8 +690,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); - const std::vector &traits = species.Traits(); - size_t trait_count = traits.size(); + slim_trait_index_t trait_count = species.TraitCount(); if (trait_count == 1) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)mut_trait_info[0].effect_size_)); @@ -701,7 +700,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); - for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { slim_effect_t effect = mut_trait_info[trait_index].effect_size_; @@ -718,7 +717,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // Note that we use RealizedDominanceForTrait() here so that an independent dominance of NAN gets handled. Species &species = mutation_type_ptr_->species_; const std::vector &traits = species.Traits(); - size_t trait_count = traits.size(); + slim_trait_index_t trait_count = species.TraitCount(); if (trait_count == 1) { @@ -734,7 +733,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); - for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[trait_index]); @@ -751,8 +750,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); - const std::vector &traits = species.Traits(); - size_t trait_count = traits.size(); + slim_trait_index_t trait_count = species.TraitCount(); if (trait_count == 1) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)mut_trait_info[0].hemizygous_dominance_coeff_)); @@ -762,7 +760,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); - for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; @@ -1243,7 +1241,7 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; - std::vector trait_indices; + std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectForTrait"); // get the trait info for this mutation @@ -1252,7 +1250,7 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; slim_effect_t effect = mut_trait_info[trait_index].effect_size_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)effect)); @@ -1261,7 +1259,7 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { slim_effect_t effect = mut_trait_info[trait_index].effect_size_; @@ -1282,13 +1280,13 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; const std::vector &traits = species.Traits(); - std::vector trait_indices; + std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "dominanceForTrait"); // get the trait info for this mutation if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; Trait *trait = traits[trait_index]; slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -1298,7 +1296,7 @@ EidosValue_SP Mutation::ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_me { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = traits[trait_index]; slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -1319,7 +1317,7 @@ EidosValue_SP Mutation::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStr // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; - std::vector trait_indices; + std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "hemizygousDominanceForTrait"); // get the trait info for this mutation @@ -1328,7 +1326,7 @@ EidosValue_SP Mutation::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStr if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)dominance)); @@ -1337,7 +1335,7 @@ EidosValue_SP Mutation::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStr { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { slim_effect_t dominance = mut_trait_info[trait_index].hemizygous_dominance_coeff_; @@ -1480,14 +1478,14 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI const std::vector &traits = species->Traits(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setEffectForTrait"); - int trait_count = (int)trait_indices.size(); + slim_trait_index_t trait_count = (slim_trait_index_t)trait_indices.size(); if (effect_value->Type() == EidosValueType::kValueNULL) { // pattern 1: drawing a default effect value for each trait in one or more mutations - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { @@ -1509,7 +1507,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { @@ -1527,7 +1525,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI Mutation *mut = mutations_buffer[mutation_index]; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; @@ -1541,7 +1539,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI // pattern 3: setting one effect value per trait, in one or more mutations int effect_index = 0; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { slim_effect_t effect = static_cast(effect_value->NumericAtIndex_NOCAST(effect_index++, nullptr)); @@ -1567,7 +1565,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { @@ -1586,7 +1584,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI Mutation *mut = mutations_buffer[mutation_index]; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t effect = static_cast(*(effects_int++)); @@ -1604,7 +1602,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { @@ -1623,7 +1621,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI Mutation *mut = mutations_buffer[mutation_index]; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t effect = static_cast(*(effects_float++)); @@ -1672,15 +1670,15 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri const std::vector &traits = species->Traits(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, method_name); - int trait_count = (int)trait_indices.size(); + slim_trait_index_t trait_count = (slim_trait_index_t)trait_indices.size(); // note there is intentionally no bounds check of dominance coefficients if (dominance_value->Type() == EidosValueType::kValueNULL) { // pattern 1: drawing a default dominance value for each trait in one or more mutations - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { @@ -1705,7 +1703,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { @@ -1726,7 +1724,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri Mutation *mut = mutations_buffer[mutation_index]; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; @@ -1743,7 +1741,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri // pattern 3: setting one dominance value per trait, in one or more mutations int dominance_index = 0; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { slim_effect_t dominance = static_cast(dominance_value->NumericAtIndex_NOCAST(dominance_index++, nullptr)); @@ -1772,7 +1770,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { @@ -1794,7 +1792,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri Mutation *mut = mutations_buffer[mutation_index]; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t dominance = static_cast(*(dominances_int++)); @@ -1815,7 +1813,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri if (trait_count == 1) { // optimized case for one trait - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { @@ -1837,7 +1835,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri Mutation *mut = mutations_buffer[mutation_index]; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; slim_effect_t dominance = static_cast(*(dominances_float++)); diff --git a/core/mutation_block.cpp b/core/mutation_block.cpp index 81d8e8f9..6cdca514 100644 --- a/core/mutation_block.cpp +++ b/core/mutation_block.cpp @@ -25,7 +25,7 @@ #define SLIM_MUTATION_BLOCK_INITIAL_SIZE 16384 // makes for about a 1 MB block; not unreasonable // NOLINT(*-macro-to-enum) : this is fine -MutationBlock::MutationBlock(Species &p_species, int p_trait_count) : species_(p_species), trait_count_(p_trait_count) +MutationBlock::MutationBlock(Species &p_species, slim_trait_index_t p_trait_count) : species_(p_species), trait_count_(p_trait_count) { THREAD_SAFETY_IN_ANY_PARALLEL("SLiM_CreateMutationBlock(): mutation_buffer_ address change"); diff --git a/core/mutation_block.h b/core/mutation_block.h index ba8db9a3..acef443c 100644 --- a/core/mutation_block.h +++ b/core/mutation_block.h @@ -52,7 +52,7 @@ class MutationBlock MutationIndex free_index_ = -1; MutationIndex last_used_index_ = -1; - int trait_count_; // the number of MutationTraitInfo records kept in trait_info_buffer_ for each mutation + slim_trait_index_t trait_count_; // the number of MutationTraitInfo records kept in trait_info_buffer_ for each mutation #ifdef DEBUG_LOCKS_ENABLED // We do not arbitrate access to the mutation block with a lock; instead, we expect that clients @@ -61,7 +61,7 @@ class MutationBlock EidosDebugLock mutation_block_LOCK("mutation_block_LOCK"); #endif - explicit MutationBlock(Species &p_species, int p_trait_count); + explicit MutationBlock(Species &p_species, slim_trait_index_t p_trait_count); ~MutationBlock(void); void IncreaseMutationBlockCapacity(void); diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index b6b2cedf..742741a8 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -99,7 +99,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr DES_info.DES_parameters_ = p_DES_parameters; DES_info.DES_strings_ = p_DES_strings; - for (int trait_index = 0; trait_index < species_.TraitCount(); trait_index++) + for (slim_trait_index_t trait_index = 0; trait_index < species_.TraitCount(); trait_index++) effect_distributions_.push_back(DES_info); // Nucleotide-based mutations use a special stacking group, -1, and always use stacking policy "l" @@ -264,7 +264,7 @@ void MutationType::SelfConsistencyCheck(const std::string &p_message_end) } } -slim_effect_t MutationType::DrawEffectForTrait(int64_t p_trait_index) const +slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) const { const EffectDistributionInfo &DES_info = effect_distributions_[p_trait_index]; @@ -717,12 +717,12 @@ EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalSt EidosValue *trait_value = p_arguments[0].get(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultDominanceForTrait"); if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)DefaultDominanceForTrait(trait_index))); } @@ -730,7 +730,7 @@ EidosValue_SP MutationType::ExecuteMethod_defaultDominanceForTrait(EidosGlobalSt { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) float_result->push_float_no_check((double)DefaultDominanceForTrait(trait_index)); return EidosValue_SP(float_result); @@ -745,12 +745,12 @@ EidosValue_SP MutationType::ExecuteMethod_defaultHemizygousDominanceForTrait(Eid EidosValue *trait_value = p_arguments[0].get(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "defaultHemizygousDominanceForTrait"); if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)DefaultHemizygousDominanceForTrait(trait_index))); } @@ -758,7 +758,7 @@ EidosValue_SP MutationType::ExecuteMethod_defaultHemizygousDominanceForTrait(Eid { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) float_result->push_float_no_check((double)DefaultHemizygousDominanceForTrait(trait_index)); return EidosValue_SP(float_result); @@ -773,14 +773,14 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos EidosValue *trait_value = p_arguments[0].get(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectDistributionParamsForTrait"); // decide whether doing floats or strings; must be the same for all bool is_float = false; bool is_string = false; - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; @@ -797,7 +797,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos { EidosValue_Float *float_result = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; @@ -811,7 +811,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos { EidosValue_String *string_result = new (gEidosValuePool->AllocateChunk()) EidosValue_String(); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; @@ -831,13 +831,13 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionTypeForTrait(EidosGl EidosValue *trait_value = p_arguments[0].get(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectDistributionTypeForTrait"); // assemble the result EidosValue_String *string_result = new (gEidosValuePool->AllocateChunk()) EidosValue_String(); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; @@ -867,7 +867,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID EidosValue *n_value = p_arguments[1].get(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "drawEffectForTrait"); // get the number of effects to draw @@ -878,7 +878,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID if ((trait_indices.size() == 1) && (num_draws == 1)) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)DrawEffectForTrait(trait_index))); } @@ -888,7 +888,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID // draw_index is the outer loop, so that we get num_draws sets of (one draw per trait) for (int64_t draw_index = 0; draw_index < num_draws; ++draw_index) - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) float_result->push_float_no_check((double)DrawEffectForTrait(trait_index)); return EidosValue_SP(float_result); @@ -905,7 +905,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba int dominance_count = dominance_value->Count(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDefaultDominanceForTrait"); if (dominance_count == 1) @@ -913,7 +913,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba // get the dominance coefficient double dominance = dominance_value->FloatAtIndex_NOCAST(0, nullptr); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; @@ -924,7 +924,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba { for (int dominance_index = 0; dominance_index < dominance_count; dominance_index++) { - int64_t trait_index = trait_indices[dominance_index]; + slim_trait_index_t trait_index = trait_indices[dominance_index]; EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; double dominance = dominance_value->FloatAtIndex_NOCAST(dominance_index, nullptr); @@ -955,7 +955,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( int dominance_count = dominance_value->Count(); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDefaultHemizygousDominanceForTrait"); if (dominance_count == 1) @@ -963,7 +963,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( // get the dominance coefficient double dominance = dominance_value->FloatAtIndex_NOCAST(0, nullptr); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; @@ -974,7 +974,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( { for (int dominance_index = 0; dominance_index < dominance_count; dominance_index++) { - int64_t trait_index = trait_indices[dominance_index]; + slim_trait_index_t trait_index = trait_indices[dominance_index]; EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; double dominance = dominance_value->FloatAtIndex_NOCAST(dominance_index, nullptr); @@ -1005,7 +1005,7 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo std::string DES_type_string = distributionType_value->StringAtIndex_NOCAST(0, nullptr); // get the trait indices, with bounds-checking - std::vector trait_indices; + std::vector trait_indices; species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setEffectDistributionForTrait"); // Parse the DES type and parameters, and do various sanity checks @@ -1020,7 +1020,7 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo species_.type_s_DESs_present_ = true; // Everything seems to be in order, so replace our distribution info (in each specified trait) with the new info - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; @@ -1035,7 +1035,7 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo // check whether our DES for all traits is now neutral; we can change from non-neutral back to neutral all_neutral_DES_ = true; - for (int64_t trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) { EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; diff --git a/core/mutation_type.h b/core/mutation_type.h index 8e71bf25..552f0cb7 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -193,21 +193,21 @@ class MutationType : public EidosDictionaryUnretained // Check that our internal state all makes sense void SelfConsistencyCheck(const std::string &p_message_end); - slim_effect_t DefaultDominanceForTrait(int64_t p_trait_index) const + slim_effect_t DefaultDominanceForTrait(slim_trait_index_t p_trait_index) const { const EffectDistributionInfo &DES_info = effect_distributions_[p_trait_index]; return DES_info.default_dominance_coeff_; } - slim_effect_t DefaultHemizygousDominanceForTrait(int64_t p_trait_index) const + slim_effect_t DefaultHemizygousDominanceForTrait(slim_trait_index_t p_trait_index) const { const EffectDistributionInfo &DES_info = effect_distributions_[p_trait_index]; return DES_info.default_hemizygous_dominance_coeff_; } - slim_effect_t DrawEffectForTrait(int64_t p_trait_index) const; // draw a selection coefficient from the DE for a trait + slim_effect_t DrawEffectForTrait(slim_trait_index_t p_trait_index) const; // draw a selection coefficient from the DE for a trait // diff --git a/core/polymorphism.cpp b/core/polymorphism.cpp index 0c61f557..09a949fc 100644 --- a/core/polymorphism.cpp +++ b/core/polymorphism.cpp @@ -51,9 +51,9 @@ void Polymorphism::Print_ID_Tag(std::ostream &p_out) const Species &species = mutation_ptr_->mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); - int trait_count = species.TraitCount(); + slim_trait_index_t trait_count = species.TraitCount(); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if (trait_index > 0) p_out << ","; @@ -64,7 +64,7 @@ void Polymorphism::Print_ID_Tag(std::ostream &p_out) const p_out << " "; - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if (trait_index > 0) p_out << ","; @@ -112,9 +112,9 @@ void Polymorphism::Print_ID(std::ostream &p_out) const Species &species = mutation_ptr_->mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); - int trait_count = species.TraitCount(); + slim_trait_index_t trait_count = species.TraitCount(); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if (trait_index > 0) p_out << ","; @@ -125,7 +125,7 @@ void Polymorphism::Print_ID(std::ostream &p_out) const p_out << " "; - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if (trait_index > 0) p_out << ","; @@ -174,9 +174,9 @@ void Polymorphism::Print_NoID_Tag(std::ostream &p_out) const // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); - int trait_count = species.TraitCount(); + slim_trait_index_t trait_count = species.TraitCount(); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if (trait_index > 0) p_out << ","; @@ -186,7 +186,7 @@ void Polymorphism::Print_NoID_Tag(std::ostream &p_out) const p_out << " "; - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if (trait_index > 0) p_out << ","; @@ -241,9 +241,9 @@ void Polymorphism::Print_NoID(std::ostream &p_out) const // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mutation_ptr_); - int trait_count = species.TraitCount(); + slim_trait_index_t trait_count = species.TraitCount(); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if (trait_index > 0) p_out << ","; @@ -253,7 +253,7 @@ void Polymorphism::Print_NoID(std::ostream &p_out) const p_out << " "; - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { if (trait_index > 0) p_out << ","; diff --git a/core/population.cpp b/core/population.cpp index 65f31dab..f4432c8e 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5452,10 +5452,10 @@ void Population::RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recal } // we need to recalculate phenotypes for traits that have a direct effect on fitness - std::vector p_direct_effect_trait_indices; + std::vector p_direct_effect_trait_indices; const std::vector &traits = species_.Traits(); - for (int trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) if (traits[trait_index]->HasDirectFitnessEffect()) p_direct_effect_trait_indices.push_back(trait_index); diff --git a/core/slim_eidos_block.h b/core/slim_eidos_block.h index 177c9edf..001230ca 100644 --- a/core/slim_eidos_block.h +++ b/core/slim_eidos_block.h @@ -158,7 +158,7 @@ class SLiMEidosBlock : public EidosDictionaryUnretained Species *ticks_spec_ = nullptr; // NOT OWNED: the species to which the block is synchronized (only active when that species is active) slim_objectid_t mutation_type_id_ = -1; // -1 if not limited by this slim_objectid_t subpopulation_id_ = -1; // -1 if not limited by this - slim_objectid_t trait_index_ = -1; // -1 if not limited by this + slim_trait_index_t trait_index_ = -1; // -1 if not limited by this slim_objectid_t interaction_type_id_ = -1; // -1 if not limited by this IndividualSex sex_specificity_ = IndividualSex::kUnspecified; // IndividualSex::kUnspecified if not limited by this int64_t chromosome_id_ = -1; // -1 if not limited by this diff --git a/core/slim_globals.h b/core/slim_globals.h index 92e1aa7a..4a85795e 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -124,6 +124,7 @@ typedef int64_t slim_mutationid_t; // identifiers for mutations, which require typedef int64_t slim_pedigreeid_t; // identifiers for pedigreed individuals; over many ticks in a large model maybe 64 bits? typedef int64_t slim_haplosomeid_t; // identifiers for pedigreed haplosomes; not user-visible, used by the tree-recording code, pedigree_id*2 + [0/1] typedef int32_t slim_polymorphismid_t; // identifiers for polymorphisms, which need only 32 bits since they are only segregating mutations +typedef int32_t slim_trait_index_t; // indices for traits; we are limited to 256 traits by SLIM_MAX_TRAITS at present, so this is plenty of room typedef float slim_effect_t; // storage of trait effects (e.g., selection coefficients) in memory-tight classes; also dominance coefficients typedef float slim_fitness_t; // storage of fitness effects (e.g., fitnessScaling values) and final individual fitness values diff --git a/core/species.cpp b/core/species.cpp index cc262422..b132fd06 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -571,7 +571,7 @@ void Species::AddTrait(Trait *p_trait) EidosGlobalStringID name_string_id = EidosStringRegistry::GlobalStringIDForString(name); // this is the main registry, and owns the retain count on every trait; it takes the caller's retain here - p_trait->SetIndex(traits_.size()); + p_trait->SetIndex((slim_trait_index_t)(traits_.size())); traits_.push_back(p_trait); // these are secondary indices that do not keep a retain on the traits @@ -580,7 +580,7 @@ void Species::AddTrait(Trait *p_trait) } // This returns the trait index for a single trait, represented by an EidosValue with an integer index or a Trait object -int64_t Species::GetTraitIndexFromEidosValue(EidosValue *trait_value, const std::string &p_method_name) +slim_trait_index_t Species::GetTraitIndexFromEidosValue(EidosValue *trait_value, const std::string &p_method_name) { int64_t trait_index; @@ -601,22 +601,22 @@ int64_t Species::GetTraitIndexFromEidosValue(EidosValue *trait_value, const std: if ((trait_index < 0) || (trait_index >= TraitCount())) EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): out-of-range trait index in " << p_method_name << "(); trait index " << trait_index << " is outside the range [0, " << (TraitCount() - 1) << "] for the species." << EidosTerminate(nullptr); - return trait_index; + return (slim_trait_index_t)trait_index; } // This returns trait indices, represented by an EidosValue with integer indices, string names, or Trait objects, or NULL for all traits -void Species::GetTraitIndicesFromEidosValue(std::vector &trait_indices, EidosValue *traits_value, const std::string &p_method_name) +void Species::GetTraitIndicesFromEidosValue(std::vector &trait_indices, EidosValue *traits_value, const std::string &p_method_name) { EidosValueType traits_value_type = traits_value->Type(); int traits_value_count = traits_value->Count(); - int trait_count = TraitCount(); + slim_trait_index_t trait_count = TraitCount(); switch (traits_value_type) { // NULL means "all traits", unlike for GetTraitIndexFromEidosValue() case EidosValueType::kValueNULL: { - for (int64_t trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) trait_indices.push_back(trait_index); break; } @@ -631,7 +631,7 @@ void Species::GetTraitIndicesFromEidosValue(std::vector &trait_indices, if ((trait_index < 0) || (trait_index >= TraitCount())) EIDOS_TERMINATION << "ERROR (Species::GetTraitIndicesFromEidosValue): out-of-range trait index in " << p_method_name << "(); trait index " << trait_index << " is outside the range [0, " << (TraitCount() - 1) << "] for the species." << EidosTerminate(nullptr); - trait_indices.push_back(trait_index); + trait_indices.push_back((slim_trait_index_t)trait_index); } break; } @@ -2536,10 +2536,10 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid community_.executing_species_ = this; // we need to recalculate phenotypes for traits that have a direct effect on fitness - std::vector p_direct_effect_trait_indices; + std::vector p_direct_effect_trait_indices; const std::vector &traits = Traits(); - for (int trait_index = 0; trait_index < TraitCount(); ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < TraitCount(); ++trait_index) if (traits[trait_index]->HasDirectFitnessEffect()) p_direct_effect_trait_indices.push_back(trait_index); @@ -2604,7 +2604,7 @@ Subpopulation *Species::SubpopulationWithName(const std::string &p_subpop_name) #pragma mark Running cycles #pragma mark - -std::vector Species::CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_objectid_t p_trait_index, int64_t p_chromosome_id) +std::vector Species::CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id) { // Callbacks are species-specific; this method calls up to the community, which manages script blocks, // but does a species-specific search. diff --git a/core/species.h b/core/species.h index 954ed278..9e78a857 100644 --- a/core/species.h +++ b/core/species.h @@ -447,7 +447,7 @@ class Species : public EidosDictionaryUnretained // Trait configuration and access inline __attribute__((always_inline)) const std::vector &Traits(void) { return traits_; } - inline __attribute__((always_inline)) int TraitCount(void) { return (int)traits_.size(); } + inline __attribute__((always_inline)) slim_trait_index_t TraitCount(void) { return (slim_trait_index_t)traits_.size(); } Trait *TraitFromName(const std::string &p_name) const; inline __attribute__((always_inline)) Trait *TraitFromStringID(EidosGlobalStringID p_string_id) const { @@ -463,15 +463,15 @@ class Species : public EidosDictionaryUnretained void MakeImplicitTrait(void); void AddTrait(Trait *p_trait); // takes over a retain count from the caller - int64_t GetTraitIndexFromEidosValue(EidosValue *trait_value, const std::string &p_method_name); // with a singleton EidosValue - void GetTraitIndicesFromEidosValue(std::vector &trait_indices, EidosValue *traits_value, const std::string &p_method_name); + slim_trait_index_t GetTraitIndexFromEidosValue(EidosValue *trait_value, const std::string &p_method_name); // with a singleton EidosValue + void GetTraitIndicesFromEidosValue(std::vector &trait_indices, EidosValue *traits_value, const std::string &p_method_name); // Memory usage void TabulateSLiMMemoryUsage_Species(SLiMMemoryUsage_Species *p_usage); // used by outputUsage() and SLiMgui profiling void DeleteAllMutationRuns(void); // for cleanup // Running cycles - std::vector CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_objectid_t p_trait_index, int64_t p_chromosome_id); + std::vector CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id); void RunInitializeCallbacks(void); void CreateAndPromulgateMutationBlock(void); void EndCurrentChromosome(bool starting_new_chromosome); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index c50b57b3..1fabc840 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1356,7 +1356,7 @@ void Subpopulation::FixNonNeutralCaches_OMP(void) // calls UpdateFitness() on each subpopulation. This method expresses demand for the traits in question, and // then produces fitness values by factoring in fitnessEffect() callbacks and fitnessScaling values. It stores // the fitness values in the appropriate places to prepare for their later use. -void Subpopulation::UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation) +void Subpopulation::UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation) { // Determine whether we are in a "pure neutral" case where we don't need to calculate individual fitness // because all individuals have neutral fitness. The simplest case where this is true is if there are no @@ -1441,7 +1441,7 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect bool f_has_fitnessEffect_callbacks = (p_fitnessEffect_callbacks.size() > 0); bool f_has_trait_direct_effects = (p_direct_effect_trait_indices.size() > 0); bool f_single_trait = (p_direct_effect_trait_indices.size() == 1); - void (Subpopulation::*_UpdateFitness_TEMPLATED)(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) = nullptr; + void (Subpopulation::*_UpdateFitness_TEMPLATED)(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) = nullptr; if (f_has_subpop_fitnessScaling) { @@ -1506,7 +1506,7 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect } template -void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) +void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) { // manage the shuffle buffer; this is not quite as fast as templatizing this flag, but it's simpler, and it // only adds overhead when fitnessEffect() callbacks are present, otherwise it get optimized out completely @@ -1514,7 +1514,7 @@ void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect slim_popsize_t *shuffle_buf = (f_has_shuffle_buffer ? species_.BorrowShuffleBuffer(parent_subpop_size_) : nullptr); slim_fitness_t subpop_fitness_scaling = (f_has_subpop_fitnessScaling ? (slim_fitness_t)subpop_fitness_scaling_ : (slim_fitness_t)0.0); // guaranteed >= 0.0 - int64_t single_trait_index = (f_has_trait_effects && f_single_trait ? p_direct_effect_trait_indices[0] : 0); + slim_trait_index_t single_trait_index = (f_has_trait_effects && f_single_trait ? p_direct_effect_trait_indices[0] : 0); for (slim_popsize_t shuffle_index = 0; shuffle_index < parent_subpop_size_; shuffle_index++) { @@ -1536,7 +1536,7 @@ void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect } else { - for (int64_t trait_index : p_direct_effect_trait_indices) + for (slim_trait_index_t trait_index : p_direct_effect_trait_indices) fitness *= trait_info[trait_index].phenotype_; // >= 0.0 for multiplicative traits } } @@ -1581,30 +1581,30 @@ void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect species_.ReturnShuffleBuffer(); } -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); // WF only: void Subpopulation::UpdateWFFitnessBuffers(void) diff --git a/core/subpopulation.h b/core/subpopulation.h index 773aba0d..4df54491 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -165,9 +165,9 @@ class Subpopulation : public EidosDictionaryUnretained // species. When not in use, that vector should still have one entry per trait, with empty/nullptr values. typedef struct _PerTraitSubpopCaches { std::vector mutationEffect_callbacks_per_trait; // NOT OWNED: mutationEffect() callbacks per subpopulation per trait - void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; - void (Individual::*IncorporateEffects_Hemizygous_TEMPLATED)(Species *species, Haplosome *haplosome, int64_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; - void (Individual::*IncorporateEffects_Diploid_TEMPLATED)(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, int64_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Hemizygous_TEMPLATED)(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Diploid_TEMPLATED)(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; } PerTraitSubpopCaches; std::vector per_trait_subpop_caches_; // one entry per trait, indexed by trait index @@ -383,10 +383,10 @@ class Subpopulation : public EidosDictionaryUnretained void FixNonNeutralCaches_OMP(void); #endif - void UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation); + void UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation); template - void _UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); + void _UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); slim_effect_t ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual); slim_fitness_t ApplyFitnessEffectCallbacks(std::vector &p_fitnessEffect_callbacks, Individual *p_individual); diff --git a/core/substitution.cpp b/core/substitution.cpp index 9cb6290b..fc68b747 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -47,11 +47,11 @@ EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), po Species &species = mutation_type_ptr_->species_; MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(&p_mutation); - int trait_count = species.TraitCount(); + slim_trait_index_t trait_count = species.TraitCount(); trait_info_ = (SubstitutionTraitInfo *)malloc(trait_count * sizeof(SubstitutionTraitInfo)); - for (int trait_index = 0; trait_index < trait_count; trait_index++) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; trait_index++) { trait_info_[trait_index].effect_size_ = mut_trait_info[trait_index].effect_size_; trait_info_[trait_index].dominance_coeff_UNSAFE_ = mut_trait_info[trait_index].dominance_coeff_UNSAFE_; // can be NAN @@ -69,7 +69,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // FIXME MULTITRAIT: This code path is hit when loading substitutions from an output file, also needs to initialize the multitrait info; this is just a // placeholder. The file being read in ought to specify per-trait values, which hasn't happened yet, so there are lots of details to be worked out... Species &species = mutation_type_ptr_->species_; - int trait_count = species.TraitCount(); + slim_trait_index_t trait_count = species.TraitCount(); trait_info_ = (SubstitutionTraitInfo *)malloc(trait_count * sizeof(SubstitutionTraitInfo)); @@ -82,7 +82,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ trait_info_[0].dominance_coeff_UNSAFE_ = p_dominance_coeff; // can be NAN trait_info_[0].hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(0); // FIXME MULTITRAIT: needs to be passed in - for (int trait_index = 1; trait_index < trait_count; trait_index++) + for (slim_trait_index_t trait_index = 1; trait_index < trait_count; trait_index++) { trait_info_[trait_index].effect_size_ = 0.0; // FIXME MULTITRAIT: needs to be passed in trait_info_[trait_index].dominance_coeff_UNSAFE_ = 0.0; // FIXME MULTITRAIT: needs to be passed in @@ -102,11 +102,10 @@ void Substitution::SelfConsistencyCheck(const std::string &p_message_end) EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): (internal error) trait_info_ is nullptr" << p_message_end << "." << EidosTerminate(); Species &species = mutation_type_ptr_->species_; - const std::vector &traits = species.Traits(); - int trait_count = (int)traits.size(); + slim_trait_index_t trait_count = species.TraitCount(); bool all_neutral_effects = true; - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { SubstitutionTraitInfo &traitInfoRec = trait_info_[trait_index]; @@ -131,7 +130,7 @@ void Substitution::SelfConsistencyCheck(const std::string &p_message_end) slim_effect_t Substitution::RealizedDominanceForTrait(Trait *p_trait) { - int64_t trait_index = p_trait->Index(); + slim_trait_index_t trait_index = p_trait->Index(); SubstitutionTraitInfo &traitInfoRec = trait_info_[trait_index]; if (std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) @@ -182,9 +181,9 @@ void Substitution::PrintForSLiMOutput(std::ostream &p_out) const // write out per-trait information // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here - int trait_count = species.TraitCount(); + slim_trait_index_t trait_count = species.TraitCount(); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { p_out << " " << trait_info_[trait_index].effect_size_ << " "; @@ -229,9 +228,9 @@ void Substitution::PrintForSLiMOutput_Tag(std::ostream &p_out) const // write out per-trait information // FIXME MULTITRAIT: Just dumping all the traits, for now; not sure what should happen here - int trait_count = species.TraitCount(); + slim_trait_index_t trait_count = species.TraitCount(); - for (int trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { p_out << " " << trait_info_[trait_index].effect_size_ << " "; @@ -310,8 +309,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) // This is not accelerated, because it's a bit tricky; each substitution could belong to a different species, // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. Species &species = mutation_type_ptr_->species_; - const std::vector &traits = species.Traits(); - size_t trait_count = traits.size(); + slim_trait_index_t trait_count = species.TraitCount(); if (trait_count == 1) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[0].effect_size_)); @@ -321,7 +319,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); - for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { slim_effect_t effect = trait_info_[trait_index].effect_size_; @@ -338,7 +336,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) // Note that we use RealizedDominanceForTrait() here so that an independent dominance of NAN gets handled. Species &species = mutation_type_ptr_->species_; const std::vector &traits = species.Traits(); - size_t trait_count = traits.size(); + slim_trait_index_t trait_count = species.TraitCount(); if (trait_count == 1) { @@ -354,7 +352,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); - for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[trait_index]); @@ -369,8 +367,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) // This is not accelerated, because it's a bit tricky; each substitution could belong to a different species, // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. Species &species = mutation_type_ptr_->species_; - const std::vector &traits = species.Traits(); - size_t trait_count = traits.size(); + slim_trait_index_t trait_count = species.TraitCount(); if (trait_count == 1) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[0].hemizygous_dominance_coeff_)); @@ -380,7 +377,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_count); - for (size_t trait_index = 0; trait_index < trait_count; ++trait_index) + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; @@ -712,12 +709,12 @@ EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_m // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; - std::vector trait_indices; + std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectForTrait"); if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; slim_effect_t effect = trait_info_[trait_index].effect_size_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)effect)); @@ -726,7 +723,7 @@ EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_m { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { slim_effect_t effect = trait_info_[trait_index].effect_size_; @@ -747,12 +744,12 @@ EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; const std::vector &traits = species.Traits(); - std::vector trait_indices; + std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "dominanceForTrait"); if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; Trait *trait = traits[trait_index]; slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -762,7 +759,7 @@ EidosValue_SP Substitution::ExecuteMethod_dominanceForTrait(EidosGlobalStringID { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = traits[trait_index]; slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -783,12 +780,12 @@ EidosValue_SP Substitution::ExecuteMethod_hemizygousDominanceForTrait(EidosGloba // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; - std::vector trait_indices; + std::vector trait_indices; species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "hemizygousDominanceForTrait"); if (trait_indices.size() == 1) { - int64_t trait_index = trait_indices[0]; + slim_trait_index_t trait_index = trait_indices[0]; slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)dominance)); @@ -797,7 +794,7 @@ EidosValue_SP Substitution::ExecuteMethod_hemizygousDominanceForTrait(EidosGloba { EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - for (int64_t trait_index : trait_indices) + for (slim_trait_index_t trait_index : trait_indices) { slim_effect_t dominance = trait_info_[trait_index].hemizygous_dominance_coeff_; diff --git a/core/trait.h b/core/trait.h index 3b833bc8..b390ff78 100644 --- a/core/trait.h +++ b/core/trait.h @@ -54,7 +54,7 @@ class Trait : public EidosDictionaryRetained private: #endif - int64_t index_; // the index of this trait within its species + slim_trait_index_t index_; // the index of this trait within its species std::string name_; // the user-visible name of this trait TraitType type_; // multiplicative or additive @@ -85,8 +85,8 @@ class Trait : public EidosDictionaryRetained explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, slim_effect_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); ~Trait(void); - inline __attribute__((always_inline)) int64_t Index(void) const { return index_; } - inline __attribute__((always_inline)) void SetIndex(int64_t p_index) { index_ = p_index; } // only from AddTrait() + inline __attribute__((always_inline)) slim_trait_index_t Index(void) const { return index_; } + inline __attribute__((always_inline)) void SetIndex(slim_trait_index_t p_index) { index_ = p_index; } // only from AddTrait() inline __attribute__((always_inline)) TraitType Type(void) const { return type_; } inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } From 3c49263e308855d56bc9eeae9e40df5269e1f127 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 1 Jan 2026 15:33:16 -0600 Subject: [PATCH 053/107] fix type "s" mutation types for multitrait --- core/mutation_type.cpp | 27 +++++++++++++++------------ core/mutation_type.h | 9 ++++----- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 742741a8..5d2e584a 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -62,7 +62,7 @@ MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_i MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings) : #endif self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStringWithPrefix('m', p_mutation_type_id)), EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(this, gSLiM_MutationType_Class))), - species_(p_species), mutation_type_id_(p_mutation_type_id), nucleotide_based_(p_nuc_based), convert_to_substitution_(false), stack_policy_(MutationStackPolicy::kStack), stack_group_(p_mutation_type_id), cached_DES_script_(nullptr) + species_(p_species), mutation_type_id_(p_mutation_type_id), nucleotide_based_(p_nuc_based), convert_to_substitution_(false), stack_policy_(MutationStackPolicy::kStack), stack_group_(p_mutation_type_id) #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES , muttype_registry_call_count_(0), keeping_muttype_registry_(false) #endif @@ -115,8 +115,11 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr MutationType::~MutationType(void) { - delete cached_DES_script_; - cached_DES_script_ = nullptr; + for (EffectDistributionInfo &des_info : effect_distributions_) + { + delete des_info.cached_DES_script_; + des_info.cached_DES_script_ = nullptr; + } #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES if (keeping_muttype_registry_) @@ -329,17 +332,17 @@ slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) EidosErrorContext error_context_save = gEidosErrorContext; // We try to do tokenization and parsing once per script, by caching the script - if (!cached_DES_script_) + if (!DES_info.cached_DES_script_) { std::string script_string = DES_info.DES_strings_[0]; - cached_DES_script_ = new EidosScript(script_string); + DES_info.cached_DES_script_ = new EidosScript(script_string); - gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, cached_DES_script_}; + gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, DES_info.cached_DES_script_}; try { - cached_DES_script_->Tokenize(); - cached_DES_script_->ParseInterpreterBlockToAST(false); + DES_info.cached_DES_script_->Tokenize(); + DES_info.cached_DES_script_->ParseInterpreterBlockToAST(false); } catch (...) { @@ -349,8 +352,8 @@ slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) TranslateErrorContextToUserScript("DrawEffectForTrait()"); } - delete cached_DES_script_; - cached_DES_script_ = nullptr; + delete DES_info.cached_DES_script_; + DES_info.cached_DES_script_ = nullptr; #ifdef DEBUG_LOCKS_ENABLED DrawEffectForTrait_InterpreterLock.end_critical(); @@ -361,14 +364,14 @@ slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) } // Execute inside try/catch so we can handle errors well - gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, cached_DES_script_}; + gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, DES_info.cached_DES_script_}; try { Community &community = species_.community_; EidosSymbolTable client_symbols(EidosSymbolTableType::kLocalVariablesTable, &community.SymbolTable()); EidosFunctionMap &function_map = community.FunctionMap(); - EidosInterpreter interpreter(*cached_DES_script_, client_symbols, function_map, &community, SLIM_OUTSTREAM, SLIM_ERRSTREAM + EidosInterpreter interpreter(*DES_info.cached_DES_script_, client_symbols, function_map, &community, SLIM_OUTSTREAM, SLIM_ERRSTREAM #ifdef SLIMGUI , community.check_infinite_loops_ #endif diff --git a/core/mutation_type.h b/core/mutation_type.h index 552f0cb7..771be138 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -68,9 +68,10 @@ typedef struct _EffectDistributionInfo { slim_effect_t default_dominance_coeff_; // the default dominance coefficient (h) inherited by mutations of this type slim_effect_t default_hemizygous_dominance_coeff_; // the default dominance coefficient (h) used when one haplosome is null - DESType DES_type_; // distribution of effect size (DES) type (f: fixed, g: gamma, e: exponential, n: normal, w: Weibull) - std::vector DES_parameters_; // DES parameters, of type double (originally float or integer type) - std::vector DES_strings_; // DES parameters, of type std::string (originally string type) + DESType DES_type_; // distribution of effect size (DES) type + std::vector DES_parameters_; // DES parameters, of type double (originally float or integer type) + std::vector DES_strings_; // DES parameters, of type std::string (originally string type) + mutable EidosScript *cached_DES_script_ = nullptr; // used by DES type 's' to hold a cached script for the DES } EffectDistributionInfo; @@ -115,8 +116,6 @@ class MutationType : public EidosDictionaryUnretained slim_usertag_t tag_value_ = SLIM_TAG_UNSET_VALUE; // a user-defined tag value - mutable EidosScript *cached_DES_script_; // used by DES type 's' to hold a cached script for the DES // FIXME MULTITRAIT move into EffectDistributionInfo - #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES // MutationType now has the ability to (optionally) keep a registry of all extant mutations of its type in the simulation, // separate from the main registry kept by Population. This allows much faster response to Species::mutationsOfType() From e0c6f26a872f89189babf41b8f177cc5c0348fc4 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 1 Jan 2026 15:42:04 -0600 Subject: [PATCH 054/107] improve error messages for mutation property sets --- core/mutation.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/mutation.cpp b/core/mutation.cpp index 0cda73a3..054c49e3 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -1132,7 +1132,8 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); slim_effect_t new_effect = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); - // FIXME MULTITRAIT: finite values only! + if (!std::isfinite(new_effect)) + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << property_string << " is required to be finite." << EidosTerminate(); SetEffect(trait, traitInfoRec, new_effect); SelfConsistencyCheck(" after setting " + property_string); @@ -1149,7 +1150,8 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); - // FIXME MULTITRAIT: finite values only! + if (!std::isfinite(new_dominance)) + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << new_dominance << " is required to be finite or NAN." << EidosTerminate(); SetHemizygousDominance(trait, traitInfoRec, new_dominance); SelfConsistencyCheck(" after setting " + property_string); @@ -1166,9 +1168,8 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); - // FIXME MULTITRAIT: NAN should be allowed, but only if (1) there is only one trait, - // or (2) the mutation is already set to independent dominance; can't change one - // dominance coefficient among many to be independent + if (std::isinf(new_dominance)) + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << new_dominance << " is required to be finite or NAN." << EidosTerminate(); SetDominance(trait, traitInfoRec, new_dominance); SelfConsistencyCheck(" after setting " + property_string); From 10fafbf0c1b3c4611c675dbd7dcb40896a971cd7 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 2 Jan 2026 09:49:29 -0600 Subject: [PATCH 055/107] extend sumOfMutationsOfType() for multitrait --- QtSLiM/help/SLiMHelpClasses.html | 12 +++++---- SLiMgui/SLiMHelpClasses.rtf | 46 +++++++++++++++++++++++--------- VERSIONS | 1 + core/haplosome.cpp | 18 ++++++++++--- core/individual.cpp | 18 ++++++++++--- 5 files changed, 69 insertions(+), 26 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index e4e7b9f1..dbb6f52b 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -300,7 +300,7 @@

In multi-chromosome models, the frequency of each mutation is assessed within the subset of target haplosomes that are associated with the same chromosome.  In other words, if a mutation is associated with chromosome 1, and the target haplosomes are associated with both chromosomes 1 and 2, the frequency of the mutation will be calculated only within the haplosomes for chromosome 1 (as you would expect).  However, you might often wish to obtain frequencies only for mutations associated with one particular chromosome.  In that case, you would probably want to pass a vector of the mutations associated with that specific chromosome, as obtained from the subsetMutations() method of Species, rather than passing NULL.  (Passing NULL in that scenario would give you frequencies of 0 for all of the mutations associated with other chromosomes in the model.)

See the +mutationCountsInHaplosomes() method to obtain integer counts instead of float frequencies.  See also the Species methods mutationCounts() and mutationFrequencies(), which might be more efficient for getting counts/frequencies for whole subpopulations or for the whole species.

 (object<Mutation>)mutationsOfType(io<MutationType>$ mutType)

-

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations in the haplosome.  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType(); if you need just the positions of matching Mutation objects, use -positionsOfMutationsOfType(); and if you are aiming for a sum of the selection coefficients of matching Mutation objects, use -sumOfMutationsOfType().  This method is provided for speed; it is much faster than the corresponding Eidos code.  See also substitutionsOfType().

+

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations in the haplosome.  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType(); if you need just the positions of matching Mutation objects, use -positionsOfMutationsOfType(); and if you are aiming for a sum of the effects of matching Mutation objects, use -sumOfMutationsOfType().  This method is provided for speed; it is much faster than the corresponding Eidos code.  See also substitutionsOfType().

– (is)nucleotides([Ni$ start = NULL], [Ni$ end = NULL], [string$ format = "string"])

Returns the nucleotide sequence for the haplosome.  This is the current ancestral sequence, as would be returned by the Chromosome method ancestralNucleotides(), with the nucleotides for any nucleotide-based mutations in the haplosome overlaid.  The range of the returned sequence may be constrained by a start position given in start and/or an end position given in end; nucleotides will be returned from start to end, inclusive.  The default value of NULL for start and end represent the first and last base positions of the chromosome, respectively.

The format of the returned sequence is controlled by the format parameter.  A format of "string" will return the sequence as a singleton string (e.g., "TATA").  A format of "char" will return a string vector with one element per nucleotide (e.g., "T", "A", "T", "A").  A format of "integer" will return an integer vector with values A=0, C=1, G=2, T=3 (e.g., 3, 0, 3, 0).  A format of "codon" will return an integer vector with values from 0 to 63, based upon successive nucleotide triplets in the sequence (which, for this format, must have a length that is a multiple of three); see the ancestralNucleotides() documentation for details.  If the sequence returned is likely to be long, the "string" format will be the most memory-efficient, and may also be the fastest (but may be harder to work with).

@@ -339,8 +339,9 @@

Remove the mutations in mutations from the target haplosomes, if they are present (if they are not present, they will be ignored).  If NULL is passed for mutations (which is the default), then all mutations will be removed from the target haplosomes; in this case, substitute must be F (a specific vector of mutations to be substituted is required).  Note that the Mutation objects removed remain valid, and will still be in the simulation’s mutation registry (i.e., will be returned by the Species property mutations), until the next tick.  All target haplosomes and all mutations in mutations must be associated with the same Chromosome object; attempting to remove a mutation from a haplosome associated with a different chromosome will raise an error.

Removing mutations will normally affect the fitness values calculated at the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

The optional parameter substitute was added in SLiM 2.2, with a default of F for backward compatibility.  If substitute is T, Substitution objects will be created for all of the removed mutations so that they are recorded in the simulation as having fixed, just as if they had reached fixation and been removed by SLiM’s own internal machinery.  This will occur regardless of whether the mutations have in fact fixed, regardless of the convertToSubstitution property of the relevant mutation types, and regardless of whether all copies of the mutations have even been removed from the simulation (making it possible to create Substitution objects for mutations that are still segregating).  It is up to the caller to perform whatever checks are necessary to preserve the integrity of the simulation’s records.  Typically substitute will only be set to T in the context of calls like sim.subpopulations.haplosomes.removeMutations(muts, T), such that the substituted mutations are guaranteed to be entirely removed from circulation.  As mentioned above, substitute may not be T if mutations is NULL.

-

 (float$)sumOfMutationsOfType(io<MutationType>$ mutType)

-

Returns the sum of the selection coefficients of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosome.  This is often useful in models that use a particular mutation type to represent QTLs with additive effects; in that context, sumOfMutationsOfType() will provide the sum of the additive effects of the QTLs for the given mutation type.  This method is provided for speed; it is much faster than the corresponding Eidos code.  Note that this method also exists on Individual, for cases in which the sum across both haplosomes of an individual is desired.

+

 (float$)sumOfMutationsOfType(io<MutationType>$ mutType, [Niso<Trait>$ trait = NULL])

+

Returns the sum of the effects of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosome, for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL can be used to represent the one defined trait in a single-trait species.

+

Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, sumOfMutationsOfType() provides the sum of the additive effects of the QTLs for the given mutation type.  With the support for quantitative traits that is now built into SLiM this method has largely been superseded, and is now provided mostly for backward compatibility.  This method is provided for speed; it is much faster than the corresponding Eidos code.  Note that this method also exists on Individual, for cases in which the sum across both haplosomes of an individual is desired.

5.5  Class GenomicElement

5.5.1  GenomicElement properties

endPosition => (integer$)

@@ -523,8 +524,9 @@

AB BA 2 (full siblings)

AA AA 2 (full siblings)

This method does not estimate consanguinity.  For example, if one individual is itself a parent of the other individual, that is irrelevant for this method.  Similarly, in simulations of sex chromosomes, the sexes of the parents are irrelevant, even if no genetic material would have been inherited from a given parent.  See relatedness() for an assessment of pedigree-based relatedness that does estimate the consanguinity of individuals.  The sharedParentCount() method is preferable if your exact question is simply whether individuals are full siblings, half siblings, or non-siblings; in other cases, relatedness() is probably more useful.

-

 (float$)sumOfMutationsOfType(io<MutationType>$ mutType)

-

Returns the sum of the selection coefficients of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosomes of the individual.  This is often useful in models that use a particular mutation type to represent QTLs with additive effects; in that context, sumOfMutationsOfType() will provide the sum of the additive effects of the QTLs for the given mutation type.  This method is provided for speed; it is much faster than the corresponding Eidos code.  Note that this method also exists on Haplosome, for cases in which the sum for just one haplosome is desired.

+

 (float$)sumOfMutationsOfType(io<MutationType>$ mutType, [Niso<Trait>$ trait = NULL])

+

Returns the sum of the effects of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosomes of the individual, for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL can be used to represent the one defined trait in a single-trait species.

+

Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, sumOfMutationsOfType() provides the sum of the additive effects of the QTLs for the given mutation type.  With the support for quantitative traits that is now built into SLiM this method has largely been superseded, and is now provided mostly for backward compatibility.  This method is provided for speed; it is much faster than the corresponding Eidos code.  Note that this method also exists on Haplosome, for cases in which the sum for just one haplosome is desired.

 (object<Mutation>)uniqueMutationsOfType(io<MutationType>$ mutType)

This method has been deprecated, and may be removed in a future release of SLiM.  Its functionality was replaced by mutationsFromHaplosomes() in SLiM 5.0.

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations in the individual.  Mutations present in both homologous haplosomes will occur only once in the result of this method, and the mutations for a given chromosomes will be given in sorted order by position, so in single-chromosome simulations this method is similar to sortBy(unique(individual.haplosomes.mutationsOfType(mutType)), "position").  (Even with a single chromosome it is not identical to that call, since if multiple mutations exist at the exact same position, they may be sorted differently by this method than they would be by sortBy().)  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType().  This method is provided for speed; it is much faster than the corresponding Eidos code.  Indeed, it is faster than just individual.haplosomes.mutationsOfType(mutType), and gives uniquing and sorting on top of that, so it is advantageous unless duplicate entries for homozygous mutations are actually needed.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index ae0b547c..24ec12e6 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -2137,7 +2137,7 @@ See the \f3\fs18 Mutation \f4\fs20 objects, use \f3\fs18 -positionsOfMutationsOfType() -\f4\fs20 ; and if you are aiming for a sum of the selection coefficients of matching +\f4\fs20 ; and if you are aiming for a sum of the effects of matching \f3\fs18 Mutation \f4\fs20 objects, use \f3\fs18 -sumOfMutationsOfType() @@ -2715,18 +2715,28 @@ The optional parameter \f3\fs18 \cf0 \kerning1\expnd0\expndtw0 \'96 \f5 \'a0 -\f3 (float$)sumOfMutationsOfType(io$\'a0mutType) +\f3 (float$)sumOfMutationsOfType(io$\'a0mutType\cf2 , [Niso$\'a0trait\'a0=\'a0NULL]\cf0 ) \f5 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 Returns the sum of the selection coefficients of all mutations that are of the type specified by +\f4\fs20 \cf2 Returns the sum of the effects of all mutations that are of the type specified by \f3\fs18 mutType -\f4\fs20 , out of all of the mutations in the haplosome. This is often useful in models that use a particular mutation type to represent QTLs with additive effects; in that context, +\f4\fs20 , out of all of the mutations in the haplosome, for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 can be used to represent the one defined trait in a single-trait species.\ +Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, \f3\fs18 sumOfMutationsOfType() -\f4\fs20 will provide the sum of the additive effects of the QTLs for the given mutation type. This method is provided for speed; it is much faster than the corresponding Eidos code. Note that this method also exists on +\f4\fs20 provides the sum of the additive effects of the QTLs for the given mutation type. With the support for quantitative traits that is now built into SLiM this method has largely been superseded, and is now provided mostly for backward compatibility. This method is provided for speed; it is much faster than the corresponding Eidos code. Note that this method also exists on \f3\fs18 Individual -\f4\fs20 , for cases in which the sum across both haplosomes of an individual is desired. -\f5 \ +\f4\fs20 , for cases in which the sum across both haplosomes of an individual is desired.\ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 \f0\b\fs22 \cf0 5.5 Class GenomicElement\ @@ -4459,18 +4469,28 @@ More specifically, this method uses the parental pedigree IDs from the pedigree \f3\fs18 \cf0 \'96 \f5 \'a0 -\f3 (float$)sumOfMutationsOfType(io$\'a0mutType) +\f3 (float$)sumOfMutationsOfType(io$\'a0mutType\cf2 , [Niso$\'a0trait\'a0=\'a0NULL]\cf0 ) \f5 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 Returns the sum of the selection coefficients of all mutations that are of the type specified by +\f4\fs20 \cf2 Returns the sum of the effects of all mutations that are of the type specified by \f3\fs18 mutType -\f4\fs20 , out of all of the mutations in the haplosomes of the individual. This is often useful in models that use a particular mutation type to represent QTLs with additive effects; in that context, +\f4\fs20 , out of all of the mutations in the haplosomes of the individual, for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 can be used to represent the one defined trait in a single-trait species.\ +Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, \f3\fs18 sumOfMutationsOfType() -\f4\fs20 will provide the sum of the additive effects of the QTLs for the given mutation type. This method is provided for speed; it is much faster than the corresponding Eidos code. Note that this method also exists on +\f4\fs20 provides the sum of the additive effects of the QTLs for the given mutation type. With the support for quantitative traits that is now built into SLiM this method has largely been superseded, and is now provided mostly for backward compatibility. This method is provided for speed; it is much faster than the corresponding Eidos code. Note that this method also exists on \f3\fs18 Haplosome -\f4\fs20 , for cases in which the sum for just one haplosome is desired. -\f5 \ +\f4\fs20 , for cases in which the sum for just one haplosome is desired.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf0 \'96 diff --git a/VERSIONS b/VERSIONS index 8c4ae5d3..fdfea0bb 100644 --- a/VERSIONS +++ b/VERSIONS @@ -126,6 +126,7 @@ multitrait branch: extend recalculateFitness() with [logical$ forceRecalc = T] option for backward compatibility, but allowing trait value recalculation not to be forced redesigned the internals of mateChoice() callbacks; there should be no user-visible consequence apart from better performance (even without mateChoice() callbacks!) add new slim_trait_index_t typedef to refer to trait indices, cleaning up a bunch of code (no user-visible impact) + extend sumOfMutationsOfType() with a [Niso$ trait = NULL] parameter to allow it to be applied to any trait (but it is mostly obsolete now) version 5.1 (Eidos version 4.1): diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 8dcd4c11..4db355e8 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -1300,14 +1300,15 @@ EidosValue_SP Haplosome::ExecuteMethod_positionsOfMutationsOfType(EidosGlobalStr return EidosValue_SP(int_result); } -// ********************* - (integer$)sumOfMutationsOfType(io$ mutType) +// ********************* - (float$)sumOfMutationsOfType(io$ mutType, [Niso$ trait = NULL]) // EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *mutType_value = p_arguments[0].get(); + EidosValue *trait_value = p_arguments[1].get(); - // FIXME MULTITRAIT: This should perhaps take a trait as its second parameter, so that it can be used with any trait; and its doc needs to be rewritten; and it should be deprecated + // might deprecate this method in future? if (IsDeferred()) EIDOS_TERMINATION << "ERROR (Haplosome::ExecuteMethod_sumOfMutationsOfType): the mutations of deferred haplosomes cannot be accessed." << EidosTerminate(); @@ -1317,6 +1318,15 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID Species &species = individual_->subpopulation_->species_; MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species.community_, &species, "sumOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK + // get the trait indices, with bounds-checking + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "sumOfMutationsOfType"); + + if (trait_indices.size() != 1) + EIDOS_TERMINATION << "ERROR (Haplosome::ExecuteMethod_sumOfMutationsOfType): sumOfMutationsOfType() requires exactly one trait to be specified." << EidosTerminate(); + + const slim_trait_index_t trait_index = trait_indices[0]; + // Sum the selection coefficients of mutations of the given type MutationBlock *mutation_block = species.SpeciesMutationBlock(); Mutation *mut_block_ptr = mutation_block->mutation_buffer_; @@ -1337,7 +1347,7 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID if (mut_ptr->mutation_type_ptr_ == mutation_type_ptr) { MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mut_index); - effect_sum += (double)mut_trait_info[0].effect_size_; + effect_sum += (double)mut_trait_info[trait_index].effect_size_; } } } @@ -2281,7 +2291,7 @@ const std::vector *Haplosome_Class::Methods(void) cons methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_outputHaplosomesToMS, kEidosValueMaskVOID))->AddString_OSN(gEidosStr_filePath, gStaticEidosValueNULL)->AddLogical_OS("append", gStaticEidosValue_LogicalF)->AddLogical_OS("filterMonomorphic", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_outputHaplosomesToVCF, kEidosValueMaskVOID))->AddString_OSN(gEidosStr_filePath, gStaticEidosValueNULL)->AddLogical_OS("outputMultiallelics", gStaticEidosValue_LogicalT)->AddLogical_OS("append", gStaticEidosValue_LogicalF)->AddLogical_OS("simplifyNucleotides", gStaticEidosValue_LogicalF)->AddLogical_OS("outputNonnucleotides", gStaticEidosValue_LogicalT)->AddLogical_OS("groupAsIndividuals", gStaticEidosValue_LogicalT)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_outputHaplosomes, kEidosValueMaskVOID))->AddString_OSN(gEidosStr_filePath, gStaticEidosValueNULL)->AddLogical_OS("append", gStaticEidosValue_LogicalF)->AddLogical_OS("objectTags", gStaticEidosValue_LogicalF)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddIntStringObject_OSN("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/individual.cpp b/core/individual.cpp index e0afc0e4..1a25897d 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -3518,7 +3518,7 @@ EidosValue_SP Individual::ExecuteMethod_sharedParentCount(EidosGlobalStringID p_ return EidosValue_SP(int_result); } -// ********************* - (integer$)sumOfMutationsOfType(io$ mutType) +// ********************* - (float$)sumOfMutationsOfType(io$ mutType, [Niso$ trait = NULL]) // EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosObject **p_elements, size_t p_elements_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -3526,7 +3526,7 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb if (p_elements_size == 0) return gStaticEidosValue_Float_ZeroVec; - // FIXME MULTITRAIT: This should perhaps take a trait as its second parameter, so that it can be used with any trait; and its doc needs to be rewritten; and it should be deprecated + // might deprecate this method in future? // SPECIES CONSISTENCY CHECK Species *species = Community::SpeciesForIndividualsVector((Individual **)p_elements, (int)p_elements_size); @@ -3537,8 +3537,18 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb species->population_.CheckForDeferralInIndividualsVector((Individual **)p_elements, p_elements_size, "Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType"); EidosValue *mutType_value = p_arguments[0].get(); + EidosValue *trait_value = p_arguments[1].get(); MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species->community_, species, "sumOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK + // get the trait indices, with bounds-checking + std::vector trait_indices; + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "sumOfMutationsOfType"); + + if (trait_indices.size() != 1) + EIDOS_TERMINATION << "ERROR (Haplosome::ExecuteMethod_sumOfMutationsOfType): sumOfMutationsOfType() requires exactly one trait to be specified." << EidosTerminate(); + + const slim_trait_index_t trait_index = trait_indices[0]; + // Sum the selection coefficients of mutations of the given type MutationBlock *mutation_block = species->SpeciesMutationBlock(); Mutation *mut_block_ptr = mutation_block->mutation_buffer_; @@ -3574,7 +3584,7 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb if (mut_ptr->mutation_type_ptr_ == mutation_type_ptr) { MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mut_index); - effect_sum += (double)mut_trait_info[0].effect_size_; + effect_sum += (double)mut_trait_info[trait_index].effect_size_; } } } @@ -4270,7 +4280,7 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setPhenotypeForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddNumeric("phenotype")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); - methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); + methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddIntStringObject_OSN("trait", gSLiM_Trait_Class, gStaticEidosValueNULL))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_uniqueMutationsOfType, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->MarkDeprecated()); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_mutationsFromHaplosomes, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddString_S("category")->AddIntObject_OSN("mutType", gSLiM_MutationType_Class, gStaticEidosValueNULL)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); From 3d9ccdaa9341c365b4113d87a3e283ed3cab75d8 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 2 Jan 2026 10:44:56 -0600 Subject: [PATCH 056/107] extend cachedFitness() to accept o --- QtSLiM/help/SLiMHelpClasses.html | 6 +- SLiMgui/SLiMHelpClasses.rtf | 30 ++++++--- VERSIONS | 1 + core/subpopulation.cpp | 103 +++++++++++++++++++++++++------ 4 files changed, 110 insertions(+), 30 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index dbb6f52b..9ca289f7 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -1249,8 +1249,10 @@

– (void)addSpatialMap(object<SpatialMap>$ map)

Adds the given SpatialMap object, map, to the subpopulation.  (The spatial map would have been previously created with a call to defineSpatialMap() on a different subpopulation; addSpatialMap() can then be used to add that existing spatial map with other subpopulations, sharing the map between subpopulations.)  If the map is already added to the target subpopulation, this method does nothing; if a different map with the same name is already added to the subpopulation, an error results (because map names must be unique within each subpopulation).  The map being added must be compatible with the target subpopulation; in particular, the spatial bounds utilized by the map must exactly match the corresponding spatial bounds for the subpopulation, and the dimensionality of the subpopulation must encompass the spatiality of the map.  For example, if the map has a spatiality of "xz" then the subpopulation must have a dimensionality of "xyz" so that it encompasses both "x" and "z", and the subpopulation’s spatial bounds for "x" and "z" must match those for the map (but the spatial bounds for "y" are unimportant, since the map does not use that dimension).

Adding a map to a subpopulation is not strictly necessary, at present; one may query a SpatialMap object directly using mapValue(), regarding points in a subpopulation, without the map actually having been added to that subpopulation.  However, it is a good idea to use addSpatialMap(), both for its compatibility check that prevents unnoticed scripting errors, and because it ensures correct display of the model in SLiMgui.

-

– (float)cachedFitness(Ni indices)

-

The fitness values calculated for the individuals at the indices given are returned.  If NULL is passed, fitness values for all individuals in the subpopulation are returned.  The fitness values returned are cached values; mutationEffect() and fitnessEffect() callbacks are therefore not called as a side effect of this method.  It is always an error to call cachedFitness() from inside a mutationEffect() or fitnessEffect() callback, since fitness values are in the middle of being set up.  In WF models, it is also an error to call cachedFitness() from a late() event, because fitness values for the new offspring generation have not yet been calculated and are undefined.  In nonWF models, the population may be a mixture of new and old individuals, so instead, NAN will be returned as the fitness of any new individuals whose fitness has not yet been calculated.  When new subpopulations are first created with addSubpop() or addSubpopSplit(), the fitness of all of the newly created individuals is considered to be 1.0 until fitness values are recalculated.

+

– (float)cachedFitness([Nio<Individual> individuals = NULL])

+

The fitness values calculated for the individuals specified by individuals are returned.  The individuals parameter may be an object vector of class Individual (all elements of which must belong to the target subpopulation), an integer vector of indices of individuals within the target subpopulation, or NULL (the default) to specify all individuals in the target subpopulation.

+

The fitness values returned are cached values; mutationEffect() and fitnessEffect() callbacks are therefore not called as a side effect of this method.  It is always an error to call cachedFitness() from inside a mutationEffect() or fitnessEffect() callback, since fitness values are in the middle of being set up.  In WF models, it is also an error to call cachedFitness() from a late() event, because fitness values for the new offspring generation have not yet been calculated and are undefined.  In nonWF models, the population may be a mixture of new and old individuals, so instead, NAN will be returned as the fitness of any new offspring whose fitness has not yet been calculated.  When new subpopulations are first created with addSubpop() or addSubpopSplit(), the fitness of all of the newly created individuals is considered to be 1.0 until fitness values are recalculated.

+

See the fitness property of class Individual for another way to access the cached fitness values for individuals.

– (void)configureDisplay([Nf center = NULL], [Nf$ scale = NULL], [Ns$ color = NULL])

This method customizes the display of the subpopulation in SLiMgui’s Population Visualization graph.  When this method is called by a model running outside SLiMgui, it will do nothing except type-checking and bounds-checking its arguments.  When called by a model running in SLiMgui, the position, size, and color of the subpopulation’s displayed circle can be controlled as specified below.

The center parameter sets the coordinates of the center of the subpopulation’s displayed circle; it must be a float vector of length two, such that center[0] provides the x-coordinate and center[1] provides the y-coordinate.  The square central area of the Population Visualization occupies scaled coordinates in [0,1] for both x and y, so the values in center must be within those bounds.  If a value of NULL is provided, SLiMgui’s default center will be used (which currently arranges subpopulations in a circle).

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 24ec12e6..dee3f2d0 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -10546,8 +10546,7 @@ The optional parameter \f4\fs20 in a \f3\fs18 late() \f4\fs20 event, which is also not advisable in general.)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 If +If \f3\fs18 forceRecalc \f4\fs20 is \f3\fs18 T @@ -10568,8 +10567,7 @@ The optional parameter \f4\fs20 . This could occur if, for example, if a \f3\fs18 mutationEffect() \f4\fs20 callback\'92s activation state has been changed.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 After this call, the fitness values used for all purposes in SLiM will be the newly calculated values. Calling this method will trigger the calling of any enabled and applicable +After this call, the fitness values used for all purposes in SLiM will be the newly calculated values. Calling this method will trigger the calling of any enabled and applicable \f3\fs18 mutationEffect() \f4\fs20 and \f3\fs18 fitnessEffect() @@ -12696,12 +12694,23 @@ Adding a map to a subpopulation is not strictly necessary, at present; one may q \f4\fs20 , both for its compatibility check that prevents unnoticed scripting errors, and because it ensures correct display of the model in SLiMgui.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 \'96\'a0(float)cachedFitness(Ni\'a0indices)\ +\f3\fs18 \cf0 \'96\'a0(float)cachedFitness(\cf2 [Nio\'a0individuals\'a0=\'a0NULL]\cf0 )\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The fitness values calculated for the individuals at the indices given are returned. If +\f4\fs20 \cf2 The fitness values calculated for the individuals specified by +\f3\fs18 individuals +\f4\fs20 are returned. The +\f3\fs18 individuals +\f4\fs20 parameter may be an +\f3\fs18 object +\f4\fs20 vector of class +\f3\fs18 Individual +\f4\fs20 (all elements of which must belong to the target subpopulation), an +\f3\fs18 integer +\f4\fs20 vector of indices of individuals within the target subpopulation, or \f3\fs18 NULL -\f4\fs20 is passed, fitness values for all individuals in the subpopulation are returned. The fitness values returned are cached values; +\f4\fs20 (the default) to specify all individuals in the target subpopulation.\ +The fitness values returned are cached values; \f3\fs18 mutationEffect() \f4\fs20 and \f3\fs18 fitnessEffect() @@ -12717,13 +12726,18 @@ Adding a map to a subpopulation is not strictly necessary, at present; one may q \f3\fs18 late() \f4\fs20 event, because fitness values for the new offspring generation have not yet been calculated and are undefined. In nonWF models, the population may be a mixture of new and old individuals, so instead, \f3\fs18 NAN -\f4\fs20 will be returned as the fitness of any new individuals whose fitness has not yet been calculated. When new subpopulations are first created with +\f4\fs20 will be returned as the fitness of any new offspring whose fitness has not yet been calculated. When new subpopulations are first created with \f3\fs18 addSubpop() \f4\fs20 or \f3\fs18 addSubpopSplit() \f4\fs20 , the fitness of all of the newly created individuals is considered to be \f3\fs18 1.0 \f4\fs20 until fitness values are recalculated.\ +See the +\f3\fs18 fitness +\f4\fs20 property of class +\f3\fs18 Individual +\f4\fs20 for another way to access the cached fitness values for individuals.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \expnd0\expndtw0\kerning0 diff --git a/VERSIONS b/VERSIONS index fdfea0bb..2c4149ff 100644 --- a/VERSIONS +++ b/VERSIONS @@ -127,6 +127,7 @@ multitrait branch: redesigned the internals of mateChoice() callbacks; there should be no user-visible consequence apart from better performance (even without mateChoice() callbacks!) add new slim_trait_index_t typedef to refer to trait indices, cleaning up a bunch of code (no user-visible impact) extend sumOfMutationsOfType() with a [Niso$ trait = NULL] parameter to allow it to be applied to any trait (but it is mostly obsolete now) + extend cachedFitness() to accept a vector of class Individual, as well as its old style of taking a vector of indices into the target subpopulation version 5.1 (Eidos version 4.1): diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 1fabc840..171ca3a1 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -10241,13 +10241,11 @@ EidosValue_SP Subpopulation::ExecuteMethod_removeSubpopulation(EidosGlobalString return gStaticEidosValueVOID; } -// ********************* - (float)cachedFitness(Ni indices) +// ********************* - (float)cachedFitness([Nio individuals = NULL]) // EidosValue_SP Subpopulation::ExecuteMethod_cachedFitness(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) - EidosValue *indices_value = p_arguments[0].get(); - // TIMING RESTRICTION if (model_type_ == SLiMModelType::kModelTypeWF) { @@ -10270,30 +10268,95 @@ EidosValue_SP Subpopulation::ExecuteMethod_cachedFitness(EidosGlobalStringID p_m // in nonWF models uncalculated fitness values for new individuals are guaranteed to be NaN, so there is no need for a check here } - bool do_all_indices = (indices_value->Type() == EidosValueType::kValueNULL); - slim_popsize_t index_count = (do_all_indices ? parent_subpop_size_ : SLiMCastToPopsizeTypeOrRaise(indices_value->Count())); - EidosValue_Float *float_return = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(index_count); - EidosValue_SP result_SP = EidosValue_SP(float_return); - const int64_t *indices = (do_all_indices ? nullptr : indices_value->IntData()); + EidosValue *individuals_value = p_arguments[0].get(); + EidosValueType individuals_type = individuals_value->Type(); - for (slim_popsize_t value_index = 0; value_index < index_count; value_index++) + if (individuals_type == EidosValueType::kValueNULL) { - slim_popsize_t index = value_index; + EidosValue_Float *float_return = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(parent_subpop_size_); + EidosValue_SP result_SP = EidosValue_SP(float_return); - if (!do_all_indices) + if (individual_cached_fitness_OVERRIDE_) { - index = SLiMCastToPopsizeTypeOrRaise(indices[value_index]); - - if (index >= parent_subpop_size_) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_cachedFitness): cachedFitness() index " << index << " out of range." << EidosTerminate(); + for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) + float_return->set_float_no_check(individual_cached_fitness_OVERRIDE_value_, individual_index); + } + else + { + for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) + float_return->set_float_no_check((double)parent_individuals_[individual_index]->cached_fitness_UNSAFE_, individual_index); + } + + return result_SP; + } + else if (individuals_type == EidosValueType::kValueInt) + { + slim_popsize_t index_count = SLiMCastToPopsizeTypeOrRaise(individuals_value->Count()); + EidosValue_Float *float_return = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(index_count); + EidosValue_SP result_SP = EidosValue_SP(float_return); + const int64_t *indices = individuals_value->IntData(); + + if (individual_cached_fitness_OVERRIDE_) + { + for (slim_popsize_t value_index = 0; value_index < index_count; value_index++) + { + slim_popsize_t individual_index = SLiMCastToPopsizeTypeOrRaise(indices[value_index]); + + if (individual_index >= parent_subpop_size_) + EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_cachedFitness): cachedFitness() individual index " << individual_index << " out of range." << EidosTerminate(); + + float_return->set_float_no_check(individual_cached_fitness_OVERRIDE_value_, value_index); + } + } + else + { + for (slim_popsize_t value_index = 0; value_index < index_count; value_index++) + { + slim_popsize_t individual_index = SLiMCastToPopsizeTypeOrRaise(indices[value_index]); + + if (individual_index >= parent_subpop_size_) + EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_cachedFitness): cachedFitness() individual index " << individual_index << " out of range." << EidosTerminate(); + + float_return->set_float_no_check((double)parent_individuals_[individual_index]->cached_fitness_UNSAFE_, value_index); + } } - double fitness = (individual_cached_fitness_OVERRIDE_ ? individual_cached_fitness_OVERRIDE_value_ : (double)parent_individuals_[index]->cached_fitness_UNSAFE_); + return result_SP; + } + else // (individuals_type == EidosValueType::kValueObject) + { + slim_popsize_t individuals_count = SLiMCastToPopsizeTypeOrRaise(individuals_value->Count()); + EidosValue_Float *float_return = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(individuals_count); + EidosValue_SP result_SP = EidosValue_SP(float_return); + Individual **individuals_data = (Individual **)individuals_value->ObjectData(); - float_return->set_float_no_check(fitness, value_index); + if (individual_cached_fitness_OVERRIDE_) + { + for (slim_popsize_t value_index = 0; value_index < individuals_count; value_index++) + { + Individual *ind = individuals_data[value_index]; + + if (ind->subpopulation_ != this) + EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_cachedFitness): cachedFitness() individual does not belong to the target subpopulation." << EidosTerminate(); + + float_return->set_float_no_check(individual_cached_fitness_OVERRIDE_value_, value_index); + } + } + else + { + for (slim_popsize_t value_index = 0; value_index < individuals_count; value_index++) + { + Individual *ind = individuals_data[value_index]; + + if (ind->subpopulation_ != this) + EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_cachedFitness): cachedFitness() individual does not belong to the target subpopulation." << EidosTerminate(); + + float_return->set_float_no_check((double)ind->cached_fitness_UNSAFE_, value_index); + } + } + + return result_SP; } - - return result_SP; } // ********************* – (No)sampleIndividuals(integer$ size, [logical$ replace = F], [No$ exclude = NULL], [Ns$ sex = NULL], [Ni$ tag = NULL], [Ni$ minAge = NULL], [Ni$ maxAge = NULL], [Nl$ migrant = NULL], [Nl$ tagL0 = NULL], [Nl$ tagL1 = NULL], [Nl$ tagL2 = NULL], [Nl$ tagL3 = NULL], [Nl$ tagL4 = NULL]) @@ -11568,7 +11631,7 @@ const std::vector *Subpopulation_Class::Methods(void) methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_addSelfed, kEidosValueMaskObject, gSLiM_Individual_Class))->AddObject_S("parent", gSLiM_Individual_Class)->AddInt_OS("count", gStaticEidosValue_Integer1)->AddLogical_OS("defer", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_takeMigrants, kEidosValueMaskVOID))->AddObject("migrants", gSLiM_Individual_Class)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_removeSubpopulation, kEidosValueMaskVOID))); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_cachedFitness, kEidosValueMaskFloat))->AddInt_N("indices")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_cachedFitness, kEidosValueMaskFloat))->AddIntObject_ON("individuals", gSLiM_Individual_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sampleIndividuals, kEidosValueMaskObject, gSLiM_Individual_Class))->AddInt_S("size")->AddLogical_OS("replace", gStaticEidosValue_LogicalF)->AddObject_OSN("exclude", gSLiM_Individual_Class, gStaticEidosValueNULL)->AddString_OSN("sex", gStaticEidosValueNULL)->AddInt_OSN("tag", gStaticEidosValueNULL)->AddInt_OSN("minAge", gStaticEidosValueNULL)->AddInt_OSN("maxAge", gStaticEidosValueNULL)->AddLogical_OSN("migrant", gStaticEidosValueNULL)->AddLogical_OSN("tagL0", gStaticEidosValueNULL)->AddLogical_OSN("tagL1", gStaticEidosValueNULL)->AddLogical_OSN("tagL2", gStaticEidosValueNULL)->AddLogical_OSN("tagL3", gStaticEidosValueNULL)->AddLogical_OSN("tagL4", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_subsetIndividuals, kEidosValueMaskObject, gSLiM_Individual_Class))->AddObject_OSN("exclude", gSLiM_Individual_Class, gStaticEidosValueNULL)->AddString_OSN("sex", gStaticEidosValueNULL)->AddInt_OSN("tag", gStaticEidosValueNULL)->AddInt_OSN("minAge", gStaticEidosValueNULL)->AddInt_OSN("maxAge", gStaticEidosValueNULL)->AddLogical_OSN("migrant", gStaticEidosValueNULL)->AddLogical_OSN("tagL0", gStaticEidosValueNULL)->AddLogical_OSN("tagL1", gStaticEidosValueNULL)->AddLogical_OSN("tagL2", gStaticEidosValueNULL)->AddLogical_OSN("tagL3", gStaticEidosValueNULL)->AddLogical_OSN("tagL4", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defineSpatialMap, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SpatialMap_Class))->AddString_S("name")->AddString_S("spatiality")->AddNumeric("values")->AddLogical_OS(gStr_interpolate, gStaticEidosValue_LogicalF)->AddNumeric_ON("valueRange", gStaticEidosValueNULL)->AddString_ON("colors", gStaticEidosValueNULL)); From 28019b037d1fcba0918b500d36b7069cc4b9c050 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 2 Jan 2026 12:31:12 -0600 Subject: [PATCH 057/107] extend identical() to allow more than two values --- EidosScribe/EidosHelpFunctions.rtf | 32 ++++++++++++++++++--------- QtSLiM/help/EidosHelpFunctions.html | 5 +++-- VERSIONS | 1 + eidos/eidos_functions.cpp | 2 +- eidos/eidos_functions_values.cpp | 15 ++++++++++--- eidos/eidos_test_functions_vector.cpp | 17 ++++++++++++++ 6 files changed, 56 insertions(+), 16 deletions(-) diff --git a/EidosScribe/EidosHelpFunctions.rtf b/EidosScribe/EidosHelpFunctions.rtf index 9bf1a608..bb6b38b2 100644 --- a/EidosScribe/EidosHelpFunctions.rtf +++ b/EidosScribe/EidosHelpFunctions.rtf @@ -3196,23 +3196,33 @@ For example, \f2\fs20 .\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf0 (logical$)identical(*\'a0x, *\'a0y)\ +\f1\fs18 \cf0 (logical$)identical(*\'a0x, *\'a0y, ...)\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\f3\fs20 \cf0 Returns a +\f3\fs20 \cf2 Returns a \f1\fs18 logical \f3\fs20 value indicating -\f0\b whether two values are identical -\f3\b0 . If +\f0\b whether two or more values are identical +\f3\b0 . For two values \f1\fs18 x \f3\fs20 and \f1\fs18 y -\f3\fs20 have exactly the same type and size, and all of their corresponding elements are exactly the same, \cf2 \expnd0\expndtw0\kerning0 -and (for matrices and arrays) their dimensions are identical, \cf0 \kerning1\expnd0\expndtw0 this will return +\f3\fs20 , this will return \f1\fs18 T -\f3\fs20 , otherwise it will return +\f3\fs20 if +\f1\fs18 x +\f3\fs20 and +\f1\fs18 y +\f3\fs20 have exactly the same type and size, and all of their corresponding elements are exactly the same, and (for matrices and arrays) their dimensions are identical; otherwise it will return \f1\fs18 F -\f3\fs20 . The test here is for +\f3\fs20 . Additional parameters beyond +\f1\fs18 x +\f3\fs20 and +\f1\fs18 y +\f3\fs20 are compared to x in the same manner, and +\f1\fs18 T +\f3\fs20 is returned only if all of the parameters are identical.\ +The test here is for \f7\i exact \f3\i0 equality; an \f1\fs18 integer @@ -3230,9 +3240,11 @@ and (for matrices and arrays) their dimensions are identical, \cf0 \kerning1\exp \f1\fs18 != \f3\fs20 . Note that \f1\fs18 identical(NULL,NULL) -\f3\fs20 is +\f3\fs20 and +\f1\fs18 identical(NAN, NAN) +\f3\fs20 are both \f1\fs18 T -\f2\fs20 .\ +\f3\fs20 .\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f1\fs18 \cf0 (*)ifelse(logical\'a0test, *\'a0trueValues, *\'a0falseValues)\ diff --git a/QtSLiM/help/EidosHelpFunctions.html b/QtSLiM/help/EidosHelpFunctions.html index be083587..e3caf350 100644 --- a/QtSLiM/help/EidosHelpFunctions.html +++ b/QtSLiM/help/EidosHelpFunctions.html @@ -268,8 +268,9 @@

Note that relative to the standard C/C++ printf()-style behavior, there are a few differences: (1) only a single escape sequence may be present in the format string, (2) the use of * to defer field width and precision values to a passed parameter is not supported, (3) only integer and float values of x are supported, (4) only the %d, %i, %o, %x, %X, %f, %F, %e, %E, %g, and %G format specifiers are supported, and (5) no length modifiers may be supplied, since Eidos does not support different sizes of the integer and float types.  Note also that the Eidos conventions of emitting INF and NAN for infinities and Not-A-Number values respectively is not honored by this function; the strings generated for such values are platform-dependent, following the implementation definition of the C++ compiler used to build Eidos, since format() calls through to snprintf() to assemble the final string values.

For example, format("A number: %+7.2f", c(-4.1, 15.375, 8)) will produce a vector with three elements: "A number:   -4.10" "A number:  +15.38" "A number:   +8.00".  The precision of .2 results in two digits after the decimal point, the minimum field width of 7 results in padding of the values on the left (with spaces) to a minimum of seven characters, the flag + causes a sign to be shown on positive values as well as negative values, and the format specifier f leads to the float values of x being formatted in base-10 decimal.  One string value is produced in the result vector for each value in the parameter x.  These values could then be merged into a single string with paste(), for example, or printed with print() or cat().

-

(logical$)identical(* x, * y)

-

Returns a logical value indicating whether two values are identical.  If x and y have exactly the same type and size, and all of their corresponding elements are exactly the same, and (for matrices and arrays) their dimensions are identical, this will return T, otherwise it will return F.  The test here is for exact equality; an integer value of 1 is not considered identical to a float value of 1.0, for example.  Elements in object values must be literally the same element, not simply identical in all of their properties.  Type promotion is never done.  For testing whether two values are the same, this is generally preferable to the use of operator == or operator !=.  Note that identical(NULL,NULL) is T.

+

(logical$)identical(* x, * y, ...)

+

Returns a logical value indicating whether two or more values are identical.  For two values x and y, this will return T if x and y have exactly the same type and size, and all of their corresponding elements are exactly the same, and (for matrices and arrays) their dimensions are identical; otherwise it will return F.  Additional parameters beyond x and y are compared to x in the same manner, and T is returned only if all of the parameters are identical.

+

The test here is for exact equality; an integer value of 1 is not considered identical to a float value of 1.0, for example.  Elements in object values must be literally the same element, not simply identical in all of their properties.  Type promotion is never done.  For testing whether two values are the same, this is generally preferable to the use of operator == or operator !=.  Note that identical(NULL,NULL) and identical(NAN, NAN) are both T.

(*)ifelse(logical test, * trueValues, * falseValues)

Returns the result of a vector conditional operation: a vector composed of values from trueValues, for indices where test is T, and values from falseValues, for indices where test is F.  The lengths of trueValues and falseValues must either be equal to 1 or to the length of test; however, trueValues and falseValues don’t need to be the same length as each other.  Furthermore, the type of trueValues and falseValues must be the same (including, if they are object type, their element type).  The return will be of the same length as test, and of the same type as trueValues and falseValues.  Each element of the return vector will be taken from the corresponding element of trueValues if the corresponding element of test is T, or from the corresponding element of falseValues if the corresponding element of test is F; if the vector from which the value is to be taken (i.e., trueValues or falseValues) has a length of 1, that single value is used repeatedly, recycling the vector.  If test, trueValues, and/or falseValues are matrices or arrays, that will be ignored by ifelse() except that the result will be of the same dimensionality as test.

This is quite similar to a function in R of the same name; note, however, that Eidos evaluates all arguments to functions calls immediately, so trueValues and falseValues will be evaluated fully regardless of the values in test, unlike in R.  Value expressions without side effects are therefore recommended.

diff --git a/VERSIONS b/VERSIONS index 2c4149ff..3aff09dc 100644 --- a/VERSIONS +++ b/VERSIONS @@ -128,6 +128,7 @@ multitrait branch: add new slim_trait_index_t typedef to refer to trait indices, cleaning up a bunch of code (no user-visible impact) extend sumOfMutationsOfType() with a [Niso$ trait = NULL] parameter to allow it to be applied to any trait (but it is mostly obsolete now) extend cachedFitness() to accept a vector of class Individual, as well as its old style of taking a vector of indices into the target subpopulation + extend the Eidos identical() function to allow comparison of more than two parameters; e.g., (logical$)identical(* x, * y, ...) version 5.1 (Eidos version 4.1): diff --git a/eidos/eidos_functions.cpp b/eidos/eidos_functions.cpp index 300a17e8..4c77a885 100644 --- a/eidos/eidos_functions.cpp +++ b/eidos/eidos_functions.cpp @@ -226,7 +226,7 @@ const std::vector &EidosInterpreter::BuiltInFunction signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("cat", Eidos_ExecuteFunction_cat, kEidosValueMaskVOID))->AddAny("x")->AddString_OS("sep", gStaticEidosValue_StringSpace)->AddLogical_OS("error", gStaticEidosValue_LogicalF)); signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("catn", Eidos_ExecuteFunction_catn, kEidosValueMaskVOID))->AddAny_O("x", gStaticEidosValue_StringEmpty)->AddString_OS("sep", gStaticEidosValue_StringSpace)->AddLogical_OS("error", gStaticEidosValue_LogicalF)); signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("format", Eidos_ExecuteFunction_format, kEidosValueMaskString))->AddString_S("format")->AddNumeric("x")); - signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("identical", Eidos_ExecuteFunction_identical, kEidosValueMaskLogical | kEidosValueMaskSingleton))->AddAny("x")->AddAny("y")); + signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("identical", Eidos_ExecuteFunction_identical, kEidosValueMaskLogical | kEidosValueMaskSingleton))->AddAny("x")->AddAny("y")->AddEllipsis()); signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("ifelse", Eidos_ExecuteFunction_ifelse, kEidosValueMaskAny))->AddLogical("test")->AddAny("trueValues")->AddAny("falseValues")); signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("isClose", Eidos_ExecuteFunction_isClose, kEidosValueMaskLogical))->AddFloat("x")->AddFloat("y")-> AddFloat_OS("rtol", EidosValue_Float_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(1e-5)))-> diff --git a/eidos/eidos_functions_values.cpp b/eidos/eidos_functions_values.cpp index 4d49a563..7e3ef992 100644 --- a/eidos/eidos_functions_values.cpp +++ b/eidos/eidos_functions_values.cpp @@ -1529,13 +1529,22 @@ EidosValue_SP Eidos_ExecuteFunction_format(const std::vector &p_a return result_SP; } -// (logical$)identical(* x, * y) +// (logical$)identical(* x, * y, ...) EidosValue_SP Eidos_ExecuteFunction_identical(const std::vector &p_arguments, __attribute__((unused)) EidosInterpreter &p_interpreter) { EidosValue *x_value = p_arguments[0].get(); - EidosValue *y_value = p_arguments[1].get(); - return IdenticalEidosValues(x_value, y_value) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF; + // BCH 1/2/2026: extending this function to now accept additional arguments beyond y, but the logic is the + // same. All arguments must be identical to x for T to be returned, otherwise F is returned. + for (size_t value_index = 1; value_index < p_arguments.size(); ++value_index) + { + EidosValue *y_value = p_arguments[value_index].get(); + + if (!IdenticalEidosValues(x_value, y_value)) + return gStaticEidosValue_LogicalF; + } + + return gStaticEidosValue_LogicalT; } // (*)ifelse(logical test, * trueValues, * falseValues) diff --git a/eidos/eidos_test_functions_vector.cpp b/eidos/eidos_test_functions_vector.cpp index 8522b208..6b8a5256 100644 --- a/eidos/eidos_test_functions_vector.cpp +++ b/eidos/eidos_test_functions_vector.cpp @@ -714,6 +714,23 @@ void _RunFunctionValueInspectionManipulationTests_g_through_l(void) EidosAssertScriptSuccess_L("identical(matrix(c(6,8,6,6), nrow=1), matrix(c(6,8,6,6), ncol=1));", false); EidosAssertScriptSuccess_L("identical(matrix(c(6,8,6,6), ncol=1), matrix(c(6,8,6,6), nrow=1));", false); + EidosAssertScriptSuccess_L("identical(NULL, NULL, NULL);", true); + EidosAssertScriptSuccess_L("identical(F, F, F);", true); + EidosAssertScriptSuccess_L("identical(T, T, T);", true); + EidosAssertScriptSuccess_L("identical(5, 5, 5);", true); + EidosAssertScriptSuccess_L("identical(5.7, 5.7, 5.7);", true); + EidosAssertScriptSuccess_L("identical(NAN, NAN, NAN);", true); + EidosAssertScriptSuccess_L("identical('foo', 'foo', 'foo');", true); + EidosAssertScriptSuccess_L("x = _Test(3); y = _Test(7); identical(x, x, x);", true); + EidosAssertScriptSuccess_L("identical(NULL, NULL, integer(0));", false); + EidosAssertScriptSuccess_L("identical(F, F, T);", false); + EidosAssertScriptSuccess_L("identical(T, T, F);", false); + EidosAssertScriptSuccess_L("identical(5, 5, 6);", false); + EidosAssertScriptSuccess_L("identical(5.7, 5.7, 6.8);", false); + EidosAssertScriptSuccess_L("identical(NAN, NAN, INF);", false); + EidosAssertScriptSuccess_L("identical('foo', 'foo', 'bar');", false); + EidosAssertScriptSuccess_L("x = _Test(3); y = _Test(7); identical(x, x, y);", false); + // ifelse() EidosAssertScriptRaise("ifelse(NULL, integer(0), integer(0));", 0, "cannot be type"); EidosAssertScriptRaise("ifelse(logical(0), NULL, integer(0));", 0, "to be the same type"); From bd3400b8c9b692a15611291ceb67d58871ca83e6 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 2 Jan 2026 12:59:15 -0600 Subject: [PATCH 058/107] add cachedFitness property to Individual --- QtSLiM/help/SLiMHelpClasses.html | 6 +- SLiMgui/SLiMHelpClasses.rtf | 36 ++++++++++- VERSIONS | 1 + core/individual.cpp | 108 +++++++++++++++++++++++++++++++ core/individual.h | 1 + core/slim_test_genetics.cpp | 77 ++++++++++++++++++++++ 6 files changed, 227 insertions(+), 2 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 9ca289f7..3cfb2246 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -380,6 +380,10 @@

5.7.1  Individual properties

age <–> (integer$)

The age of the individual, measured in cycles.  A newly generated offspring individual will have an age of 0 in the same tick in which it was created.  The age of every individual is incremented by one at the same point that its species cycle counter is incremented, at the end of the tick cycle, if and only if its species was active in that tick.  The age of individuals may be changed; usually this only makes sense when setting up the initial state of a model, however.

+

cachedFitness => (float$)

+

The fitness value calculated for the individual.  The fitness value returned is a cached value; mutationEffect() and fitnessEffect() callbacks are therefore not called as a side effect of this method.

+

It is always an error to access cachedFitness from inside a mutationEffect() or fitnessEffect() callback, since fitness values are in the middle of being set up.  In WF models, it is also an error to access cachedFitness from a late() event, because fitness values for the new offspring generation have not yet been calculated and are undefined.  In nonWF models, the population may be a mixture of new and old individuals, so instead, NAN will be returned as the fitness of any new offspring whose fitness has not yet been calculated.  When new subpopulations are first created with addSubpop() or addSubpopSplit(), the fitness of all of the newly created individuals is considered to be 1.0 until fitness values are recalculated.

+

See the cachedFitness() method of class Subpopulation for another way to access the cached fitness values for individuals.

color <–> (string$)

The color used to display the individual in SLiMgui.  Outside of SLiMgui, this property still exists, but is not used by SLiM.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB" (see the Eidos manual).  If color is the empty string, "", SLiMgui’s default (fitness-based) color scheme is used; this is the default for new Individual objects.  Note that named colors will be converted to RGB internally, so the value of this property will always be a hexadecimal RGB color string (or "").

fitnessScaling <–> (float$)

@@ -1252,7 +1256,7 @@

– (float)cachedFitness([Nio<Individual> individuals = NULL])

The fitness values calculated for the individuals specified by individuals are returned.  The individuals parameter may be an object vector of class Individual (all elements of which must belong to the target subpopulation), an integer vector of indices of individuals within the target subpopulation, or NULL (the default) to specify all individuals in the target subpopulation.

The fitness values returned are cached values; mutationEffect() and fitnessEffect() callbacks are therefore not called as a side effect of this method.  It is always an error to call cachedFitness() from inside a mutationEffect() or fitnessEffect() callback, since fitness values are in the middle of being set up.  In WF models, it is also an error to call cachedFitness() from a late() event, because fitness values for the new offspring generation have not yet been calculated and are undefined.  In nonWF models, the population may be a mixture of new and old individuals, so instead, NAN will be returned as the fitness of any new offspring whose fitness has not yet been calculated.  When new subpopulations are first created with addSubpop() or addSubpopSplit(), the fitness of all of the newly created individuals is considered to be 1.0 until fitness values are recalculated.

-

See the fitness property of class Individual for another way to access the cached fitness values for individuals.

+

See the cachedFitness property of class Individual for another way to access the cached fitness values for individuals.

– (void)configureDisplay([Nf center = NULL], [Nf$ scale = NULL], [Ns$ color = NULL])

This method customizes the display of the subpopulation in SLiMgui’s Population Visualization graph.  When this method is called by a model running outside SLiMgui, it will do nothing except type-checking and bounds-checking its arguments.  When called by a model running in SLiMgui, the position, size, and color of the subpopulation’s displayed circle can be controlled as specified below.

The center parameter sets the coordinates of the center of the subpopulation’s displayed circle; it must be a float vector of length two, such that center[0] provides the x-coordinate and center[1] provides the y-coordinate.  The square central area of the Population Visualization occupies scaled coordinates in [0,1] for both x and y, so the values in center must be within those bounds.  If a value of NULL is provided, SLiMgui’s default center will be used (which currently arranges subpopulations in a circle).

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index dee3f2d0..442dca96 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -2937,6 +2937,40 @@ The species to which the target object belongs.\ \f4\i0 its species was active in that tick. The age of individuals may be changed; usually this only makes sense when setting up the initial state of a model, however.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 cachedFitness => (float$)\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The fitness value calculated for the individual. The fitness value returned is a cached value; +\f3\fs18 mutationEffect() +\f4\fs20 and +\f3\fs18 fitnessEffect() +\f4\fs20 callbacks are therefore not called as a side effect of this method.\ +It is always an error to access +\f3\fs18 cachedFitness +\f4\fs20 from inside a +\f3\fs18 mutationEffect() +\f4\fs20 or +\f3\fs18 fitnessEffect() +\f4\fs20 callback, since fitness values are in the middle of being set up. In WF models, it is also an error to access +\f3\fs18 cachedFitness +\f4\fs20 from a +\f3\fs18 late() +\f4\fs20 event, because fitness values for the new offspring generation have not yet been calculated and are undefined. In nonWF models, the population may be a mixture of new and old individuals, so instead, +\f3\fs18 NAN +\f4\fs20 will be returned as the fitness of any new offspring whose fitness has not yet been calculated. When new subpopulations are first created with +\f3\fs18 addSubpop() +\f4\fs20 or +\f3\fs18 addSubpopSplit() +\f4\fs20 , the fitness of all of the newly created individuals is considered to be +\f3\fs18 1.0 +\f4\fs20 until fitness values are recalculated.\ +See the +\f3\fs18 cachedFitness() +\f4\fs20 method of class +\f3\fs18 Subpopulation +\f4\fs20 for another way to access the cached fitness values for individuals.\ +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf0 color <\'96> (string$)\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 @@ -12734,7 +12768,7 @@ The fitness values returned are cached values; \f3\fs18 1.0 \f4\fs20 until fitness values are recalculated.\ See the -\f3\fs18 fitness +\f3\fs18 cachedFitness \f4\fs20 property of class \f3\fs18 Individual \f4\fs20 for another way to access the cached fitness values for individuals.\ diff --git a/VERSIONS b/VERSIONS index 3aff09dc..24d039b0 100644 --- a/VERSIONS +++ b/VERSIONS @@ -129,6 +129,7 @@ multitrait branch: extend sumOfMutationsOfType() with a [Niso$ trait = NULL] parameter to allow it to be applied to any trait (but it is mostly obsolete now) extend cachedFitness() to accept a vector of class Individual, as well as its old style of taking a vector of indices into the target subpopulation extend the Eidos identical() function to allow comparison of more than two parameters; e.g., (logical$)identical(* x, * y, ...) + add a cachedFitness property to class Individual to provide another way of accessing fitness values for individuals, besides the cachedFitness() method of Subpopulation version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index 1a25897d..79718489 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -1406,6 +1406,34 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(age_)); } + case gID_cachedFitness: // ACCELERATED + { + // see Subpopulation::ExecuteMethod_cachedFitness() for comments on the implementation + Species &species = subpopulation_->species_; + Community &community = species.community_; + + // TIMING RESTRICTION + if (community.ModelType() == SLiMModelType::kModelTypeWF) + { + if (community.executing_species_ == &species) + if (community.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): cachedFitness may not be accessed for the currently executing species while its fitness values are being calculated." << EidosTerminate(); + + if ((community.CycleStage() == SLiMCycleStage::kWFStage5ExecuteLateScripts) && !species.has_recalculated_fitness_) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): cachedFitness generally cannot be accessed during late() events in WF models, since the new generation does not yet have fitness values (which are calculated immediately after late() events have executed). If you really need to get fitness values in a late() event, you can call recalculateFitness() first to force fitness value recalculation to occur, but that is not something to do lightly; proceed with caution. Usually it is better to access fitness values after SLiM has calculated them, in a first() or early() event." << EidosTerminate(); + } + else + { + if (community.executing_species_ == &species) + if (community.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): cachedFitness may not be accessed for the currently executing species while its fitness values are being calculated." << EidosTerminate(); + } + + Subpopulation *subpop = subpopulation_; + double fitness = subpop->individual_cached_fitness_OVERRIDE_ ? subpop->individual_cached_fitness_OVERRIDE_value_ : cached_fitness_UNSAFE_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(fitness)); + } case gID_meanParentAge: { if (mean_parent_age_ == -1) @@ -1867,6 +1895,85 @@ EidosValue *Individual::GetProperty_Accelerated_age(EidosGlobalStringID p_proper return int_result; } +EidosValue *Individual::GetProperty_Accelerated_cachedFitness(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) +{ +#pragma unused (p_property_id) + // see Subpopulation::ExecuteMethod_cachedFitness() for comments on the implementation + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); + + if (p_values_size == 0) + return float_result; + + // SPECIES CONSISTENCY CHECK + Individual **individuals = (Individual **)p_values; + Species *species = Community::SpeciesForIndividualsVector(individuals, (int)p_values_size); + + if (!species) + return nullptr; // defer to GetProperty(); this case is not optimized + + Community &community = species->community_; + + // TIMING RESTRICTION + if (community.ModelType() == SLiMModelType::kModelTypeWF) + { + if (community.executing_species_ == species) + if (community.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): cachedFitness may not be accessed for the currently executing species while its fitness values are being calculated." << EidosTerminate(); + + if ((community.CycleStage() == SLiMCycleStage::kWFStage5ExecuteLateScripts) && !species->has_recalculated_fitness_) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): cachedFitness generally cannot be accessed during late() events in WF models, since the new generation does not yet have fitness values (which are calculated immediately after late() events have executed). If you really need to get fitness values in a late() event, you can call recalculateFitness() first to force fitness value recalculation to occur, but that is not something to do lightly; proceed with caution. Usually it is better to access fitness values after SLiM has calculated them, in a first() or early() event." << EidosTerminate(); + } + else + { + if (community.executing_species_ == species) + if (community.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): cachedFitness may not be accessed for the currently executing species while its fitness values are being calculated." << EidosTerminate(); + } + + // determine whether all individuals belong to the same subpopulation + Subpopulation *consensus_subpop = individuals[0]->subpopulation_; + + for (size_t value_index = 1; value_index < p_values_size; ++value_index) + if (individuals[value_index]->subpopulation_ != consensus_subpop) + { + consensus_subpop = nullptr; + break; + } + + if (consensus_subpop) + { + if (consensus_subpop->individual_cached_fitness_OVERRIDE_) + { + double fitness = consensus_subpop->individual_cached_fitness_OVERRIDE_value_; + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + float_result->set_float_no_check(fitness, value_index); + } + else + { + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + Individual *ind = (Individual *)(p_values[value_index]); + + float_result->set_float_no_check(ind->cached_fitness_UNSAFE_, value_index); + } + } + } + else + { + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + Individual *ind = (Individual *)(p_values[value_index]); + Subpopulation *subpop = ind->subpopulation_; + double fitness = (subpop->individual_cached_fitness_OVERRIDE_ ? subpop->individual_cached_fitness_OVERRIDE_value_ : ind->cached_fitness_UNSAFE_); + + float_result->set_float_no_check(fitness, value_index); + } + } + + return float_result; +} + EidosValue *Individual::GetProperty_Accelerated_reproductiveOutput(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { #pragma unused (p_property_id) @@ -4245,6 +4352,7 @@ const std::vector *Individual_Class::Properties(void properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_yz, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_xyz, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_age, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Individual::GetProperty_Accelerated_age)->DeclareAcceleratedSet(Individual::SetProperty_Accelerated_age)); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_cachedFitness, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Individual::GetProperty_Accelerated_cachedFitness)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_meanParentAge, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_pedigreeID, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Individual::GetProperty_Accelerated_pedigreeID)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_pedigreeParentIDs, true, kEidosValueMaskInt))); diff --git a/core/individual.h b/core/individual.h index 03a71e0a..ea908a34 100644 --- a/core/individual.h +++ b/core/individual.h @@ -360,6 +360,7 @@ class Individual : public EidosDictionaryUnretained static EidosValue *GetProperty_Accelerated_tagL4(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_age(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_cachedFitness(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_reproductiveOutput(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_tagF(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_migrant(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 2186d069..92d7f733 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1259,6 +1259,83 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.heightDominance = NAN; if (allClose(muts.dominance, 0.4999875)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.heightDominance = NAN; if (allClose(muts.heightDominance, 0.4999875)) stop(); }"); + // Test the new Individual cachedFitness property and crosscheck it against the Subpopulation cachedFitness() method + std::string cachedFitness1 = // neutral but with fitnessScaling + R"V0G0N( + initialize() { + initializeMutationRate(1e-7); + initializeMutationType("m1", 0.5, "f", 0.0); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 9999999); + initializeRecombinationRate(1e-8); + } + 1 early() { + sim.addSubpop("p1", 50); + } + 1:10 early() { + // will affect the next generation, not this one + p1.fitnessScaling = runif(1, 0.1, 1.0); + p1.individuals.fitnessScaling = runif(50, 0.1, 1.0); + + f1 = p1.cachedFitness(NULL); + f2 = p1.cachedFitness(0:49); + f3 = p1.cachedFitness(p1.individuals); + f4 = p1.individuals.cachedFitness; + + if (!identical(f1, f2, f3, f4)) + stop(); + } + )V0G0N"; + + SLiMAssertScriptSuccess(cachedFitness1); + + std::string cachedFitness2 = // non-neutral + R"V0G0N( + initialize() { + initializeMutationRate(1e-7); + initializeMutationType("m1", 0.5, "n", 0.0, 0.01); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 9999999); + initializeRecombinationRate(1e-8); + } + 1 early() { + sim.addSubpop("p1", 50); + } + 1:10 early() { + f1 = p1.cachedFitness(NULL); + f2 = p1.cachedFitness(0:49); + f3 = p1.cachedFitness(p1.individuals); + f4 = p1.individuals.cachedFitness; + + if (!identical(f1, f2, f3, f4)) + stop(); + } + )V0G0N"; + + SLiMAssertScriptSuccess(cachedFitness2); + + std::string cachedFitness3 = // non-neutral with independent dominance + R"V0G0N( + initialize() { + initializeMutationRate(1e-7); + initializeMutationType("m1", NAN, "n", 0.0, 0.01); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 9999999); + initializeRecombinationRate(1e-8); + } + 1 early() { + sim.addSubpop("p1", 50); + } + 1:10 early() { + f1 = p1.individuals.cachedFitness; + f2 = sapply(p1.individuals, "muts = applyValue.haplosomes.mutations; product(1.0 + muts.effect * muts.dominance);"); + if (!allClose(f1, f2)) + stop(); + } + )V0G0N"; + + SLiMAssertScriptSuccess(cachedFitness3); + std::cout << "_RunMultitraitTests() done" << std::endl; } From 109cc97ebb9a36b9a8c98beb232aa655866bbe2f Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 3 Jan 2026 12:29:10 -0600 Subject: [PATCH 059/107] vectorize deleteFile() and fileExists() --- VERSIONS | 1 + eidos/eidos_functions.cpp | 4 ++-- eidos/eidos_functions_files.cpp | 38 +++++++++++++++++++++++---------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/VERSIONS b/VERSIONS index 24d039b0..7b269359 100644 --- a/VERSIONS +++ b/VERSIONS @@ -130,6 +130,7 @@ multitrait branch: extend cachedFitness() to accept a vector of class Individual, as well as its old style of taking a vector of indices into the target subpopulation extend the Eidos identical() function to allow comparison of more than two parameters; e.g., (logical$)identical(* x, * y, ...) add a cachedFitness property to class Individual to provide another way of accessing fitness values for individuals, besides the cachedFitness() method of Subpopulation + extend the Eidos deleteFile() and fileExists() functions to support passing in a vector of file paths version 5.1 (Eidos version 4.1): diff --git a/eidos/eidos_functions.cpp b/eidos/eidos_functions.cpp index 4c77a885..3aeede9b 100644 --- a/eidos/eidos_functions.cpp +++ b/eidos/eidos_functions.cpp @@ -375,8 +375,8 @@ const std::vector &EidosInterpreter::BuiltInFunction signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("createDirectory", Eidos_ExecuteFunction_createDirectory, kEidosValueMaskLogical | kEidosValueMaskSingleton))->AddString_S("path")); signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("filesAtPath", Eidos_ExecuteFunction_filesAtPath, kEidosValueMaskString))->AddString_S("path")->AddLogical_OS("fullPaths", gStaticEidosValue_LogicalF)); signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("getwd", Eidos_ExecuteFunction_getwd, kEidosValueMaskString | kEidosValueMaskSingleton))); - signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("deleteFile", Eidos_ExecuteFunction_deleteFile, kEidosValueMaskLogical | kEidosValueMaskSingleton))->AddString_S(gEidosStr_filePath)); - signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("fileExists", Eidos_ExecuteFunction_fileExists, kEidosValueMaskLogical | kEidosValueMaskSingleton))->AddString_S(gEidosStr_filePath)); + signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("deleteFile", Eidos_ExecuteFunction_deleteFile, kEidosValueMaskLogical))->AddString(gEidosStr_filePath)); + signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("fileExists", Eidos_ExecuteFunction_fileExists, kEidosValueMaskLogical))->AddString(gEidosStr_filePath)); signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("flushFile", Eidos_ExecuteFunction_flushFile, kEidosValueMaskLogical | kEidosValueMaskSingleton))->AddString_S(gEidosStr_filePath)); signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("readFile", Eidos_ExecuteFunction_readFile, kEidosValueMaskString))->AddString_S(gEidosStr_filePath)); signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("readLine", Eidos_ExecuteFunction_readLine, kEidosValueMaskString | kEidosValueMaskSingleton))); diff --git a/eidos/eidos_functions_files.cpp b/eidos/eidos_functions_files.cpp index e1ea2000..22a481ec 100644 --- a/eidos/eidos_functions_files.cpp +++ b/eidos/eidos_functions_files.cpp @@ -61,7 +61,7 @@ EidosValue_SP Eidos_ExecuteFunction_createDirectory(const std::vector &p_arguments, __attribute__((unused)) EidosInterpreter &p_interpreter) { // Note that this function ignores matrix/array attributes, and always returns a vector, by design @@ -69,15 +69,24 @@ EidosValue_SP Eidos_ExecuteFunction_deleteFile(const std::vector EidosValue_SP result_SP(nullptr); EidosValue *filePath_value = p_arguments[0].get(); - std::string base_path = filePath_value->StringAtIndex_NOCAST(0, nullptr); - std::string file_path = Eidos_ResolvedPath(base_path); + int filePath_count = filePath_value->Count(); + EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(filePath_count); + result_SP = EidosValue_SP(logical_result); - result_SP = ((remove(file_path.c_str()) == 0) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + for (int value_index = 0; value_index < filePath_count; ++value_index) + { + std::string base_path = filePath_value->StringAtIndex_NOCAST(value_index, nullptr); + std::string file_path = Eidos_ResolvedPath(base_path); + + bool success = (remove(file_path.c_str()) == 0); + + logical_result->set_logical_no_check(success, value_index); + } return result_SP; } -// (logical$)fileExists(string$ filePath) +// (logical)fileExists(string filePath) EidosValue_SP Eidos_ExecuteFunction_fileExists(const std::vector &p_arguments, __attribute__((unused)) EidosInterpreter &p_interpreter) { // Note that this function ignores matrix/array attributes, and always returns a vector, by design @@ -85,13 +94,20 @@ EidosValue_SP Eidos_ExecuteFunction_fileExists(const std::vector EidosValue_SP result_SP(nullptr); EidosValue *filePath_value = p_arguments[0].get(); - std::string base_path = filePath_value->StringAtIndex_NOCAST(0, nullptr); - std::string file_path = Eidos_ResolvedPath(base_path); - - struct stat file_info; - bool path_exists = (stat(file_path.c_str(), &file_info) == 0); + int filePath_count = filePath_value->Count(); + EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(filePath_count); + result_SP = EidosValue_SP(logical_result); - result_SP = (path_exists ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + for (int value_index = 0; value_index < filePath_count; ++value_index) + { + std::string base_path = filePath_value->StringAtIndex_NOCAST(value_index, nullptr); + std::string file_path = Eidos_ResolvedPath(base_path); + + struct stat file_info; + bool path_exists = (stat(file_path.c_str(), &file_info) == 0); + + logical_result->set_logical_no_check(path_exists, value_index); + } return result_SP; } From 00e358b8b5f38d8d1d6e74af93e0c31697d6a712 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 3 Jan 2026 12:30:15 -0600 Subject: [PATCH 060/107] disable filesAtPath() tests for tempdir() (very slow) --- eidos/eidos_test_functions_other.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eidos/eidos_test_functions_other.cpp b/eidos/eidos_test_functions_other.cpp index 5f44cf86..6cda847f 100644 --- a/eidos/eidos_test_functions_other.cpp +++ b/eidos/eidos_test_functions_other.cpp @@ -395,10 +395,12 @@ void _RunFunctionFilesystemTests(const std::string &temp_path) return; // filesAtPath() – hard to know how to test this! These tests should be true on Un*x machines, anyway – but might be disallowed by file permissions. - EidosAssertScriptSuccess_L("type(filesAtPath(tempdir())) == 'string';", true); + // BCH 1/3/2025: I'm commenting out the tests that call filesAtPath() on the temporary directory; that + // can contain a very large number of files, and is showing up as a major time-sink for self-tests. + //EidosAssertScriptSuccess_L("type(filesAtPath(tempdir())) == 'string';", true); // these always fail on Windows and I can't think of any good easy replacement #ifndef _WIN32 - EidosAssertScriptSuccess_L("type(filesAtPath('/tmp/')) == 'string';", true); + //EidosAssertScriptSuccess_L("type(filesAtPath('/tmp/')) == 'string';", true); EidosAssertScriptSuccess("sum(filesAtPath('/') == 'bin');", gStaticEidosValue_Integer1); EidosAssertScriptSuccess("sum(filesAtPath('/', T) == '/bin');", gStaticEidosValue_Integer1); #endif From 818c4ee5d207a89992ec5d766ed3e72228832acb Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 3 Jan 2026 12:32:08 -0600 Subject: [PATCH 061/107] doc fix for deleteFile() and fileExists(), whoops --- EidosScribe/EidosHelpFunctions.rtf | 21 ++++++++++++--------- QtSLiM/help/EidosHelpFunctions.html | 8 ++++---- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/EidosScribe/EidosHelpFunctions.rtf b/EidosScribe/EidosHelpFunctions.rtf index bb6b38b2..60e75bee 100644 --- a/EidosScribe/EidosHelpFunctions.rtf +++ b/EidosScribe/EidosHelpFunctions.rtf @@ -5374,11 +5374,11 @@ Returns the transpose \f2 \ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf0 (logical$)deleteFile(string$\'a0filePath) +\f1\fs18 \cf0 (logical)deleteFile(string\'a0filePath) \f2 \ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\f0\b\fs20 \cf0 Deletes the file +\f0\b\fs20 \cf2 Deletes the file \f3\b0 specified by \f1\fs18 filePath \f3\fs20 and returns a @@ -5387,9 +5387,10 @@ Returns the transpose \f1\fs18 T \f3\fs20 ) or failed ( \f1\fs18 F -\f3\fs20 ).\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 This function might also be able to delete a directory at +\f3\fs20 ). If +\f1\fs18 filePath +\f3\fs20 contains more than one file path, deletion is attempted for each path, and a vector of corresponding flags indicating the success or failure of each operation is returned.\ +This function might also be able to delete a directory at \f1\fs18 filePath \f3\fs20 , but only if it is empty (apart from the \f1\fs18 . @@ -5402,10 +5403,10 @@ Returns the transpose \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f1\fs18 \cf2 \expnd0\expndtw0\kerning0 -(logical$)fileExists(string$\'a0filePath)\ +(logical)fileExists(string\'a0filePath)\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 -\f0\b\fs20 \cf2 Checks the existence of the file +\f0\b\fs20 \cf2 \kerning1\expnd0\expndtw0 Checks the existence of the file \f3\b0 specified by \f1\fs18 filePath \f3\fs20 and returns a @@ -5414,10 +5415,12 @@ Returns the transpose \f1\fs18 T \f3\fs20 ) or does not exist ( \f1\fs18 F -\f3\fs20 ). This also works for directories.\ +\f3\fs20 ). This also works for directories. If +\f1\fs18 filePath +\f3\fs20 contains more than one file path, each path is checked for existence, and a vector of corresponding flags indicating the existence of each path is returned.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf0 \kerning1\expnd0\expndtw0 (string)filesAtPath(string$\'a0path, [logical$\'a0fullPaths\'a0=\'a0F]) +\f1\fs18 \cf0 (string)filesAtPath(string$\'a0path, [logical$\'a0fullPaths\'a0=\'a0F]) \f2 \ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 diff --git a/QtSLiM/help/EidosHelpFunctions.html b/QtSLiM/help/EidosHelpFunctions.html index e3caf350..914de2c1 100644 --- a/QtSLiM/help/EidosHelpFunctions.html +++ b/QtSLiM/help/EidosHelpFunctions.html @@ -420,11 +420,11 @@

3.9.  Filesystem access functions

(logical$)createDirectory(string$ path)

Creates a new filesystem directory at the path specified by path and returns a logical value indicating if the creation succeeded (T) or failed (F).  If the path already exists, createDirectory() will do nothing to the filesystem, will emit a warning, and will return T to indicate success if the existing path is a directory, or F to indicate failure if the existing path is not a directory.

-

(logical$)deleteFile(string$ filePath)

-

Deletes the file specified by filePath and returns a logical value indicating if the deletion succeeded (T) or failed (F).

+

(logical)deleteFile(string filePath)

+

Deletes the file specified by filePath and returns a logical value indicating if the deletion succeeded (T) or failed (F).  If filePath contains more than one file path, deletion is attempted for each path, and a vector of corresponding flags indicating the success or failure of each operation is returned.

This function might also be able to delete a directory at filePath, but only if it is empty (apart from the . and .. directory entries that exist on Un*x filesystems).  If other files (including invisible files) exist in the directory, deleteFile() will probably fail as a safety measure, in which case the contained files must be deleted individually first.  This is vague because the actual policy regarding deletion of directories will depend upon the operating system, since Eidos achieves the deletion by calling an operating-system function.

-

(logical$)fileExists(string$ filePath)

-

Checks the existence of the file specified by filePath and returns a logical value indicating if it exists (T) or does not exist (F).  This also works for directories.

+

(logical)fileExists(string filePath)

+

Checks the existence of the file specified by filePath and returns a logical value indicating if it exists (T) or does not exist (F).  This also works for directories.  If filePath contains more than one file path, each path is checked for existence, and a vector of corresponding flags indicating the existence of each path is returned.

(string)filesAtPath(string$ path, [logical$ fullPaths = F])

Returns a string vector containing the names of all files in a directory specified by path.  If the optional parameter fullPaths is T, full filesystem paths are returned for each file; if fullPaths is F (the default), then only the filenames relative to the specified directory are returned.  This list includes directories (i.e. subfolders), including the "." and ".." directories on Un*x systems.  The list also includes invisible files, such as those that begin with a "." on Un*x systems.  This function does not descend recursively into subdirectories.  If an error occurs during the read, NULL will be returned.

(logical$)flushFile(string$ filePath)

From 30feeaaa14001d9ed3cb3b35e728f3c419c255a1 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 3 Jan 2026 12:49:52 -0600 Subject: [PATCH 062/107] extend operator + for mixes of logical and integer --- VERSIONS | 1 + eidos/eidos_interpreter.cpp | 134 +++++++++++++++++++++- eidos/eidos_test_operators_arithmetic.cpp | 17 ++- 3 files changed, 148 insertions(+), 4 deletions(-) diff --git a/VERSIONS b/VERSIONS index 7b269359..33ef6c5e 100644 --- a/VERSIONS +++ b/VERSIONS @@ -131,6 +131,7 @@ multitrait branch: extend the Eidos identical() function to allow comparison of more than two parameters; e.g., (logical$)identical(* x, * y, ...) add a cachedFitness property to class Individual to provide another way of accessing fitness values for individuals, besides the cachedFitness() method of Subpopulation extend the Eidos deleteFile() and fileExists() functions to support passing in a vector of file paths + extend the Eidos + operator to support adding logical+logical, integer+logical, and logical+integer (all for NxN, 1xN, Nx1), as in R version 5.1 (Eidos version 4.1): diff --git a/eidos/eidos_interpreter.cpp b/eidos/eidos_interpreter.cpp index 378c305c..d9ca743c 100644 --- a/eidos/eidos_interpreter.cpp +++ b/eidos/eidos_interpreter.cpp @@ -2183,7 +2183,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Plus(const EidosASTNode *p_node) } else { - // binary plus is legal either between two numeric types, or between a string and any other non-NULL operand + // binary plus is legal either between two numeric types, or between logical and logical, or between integer and logical, or between a string and any other non-NULL operand EidosValue_SP first_child_value = FastEvaluateNode(p_node->children_[0]); EidosValueType first_child_type = first_child_value->Type(); @@ -2348,6 +2348,138 @@ EidosValue_SP EidosInterpreter::Evaluate_Plus(const EidosASTNode *p_node) EIDOS_TERMINATION << "ERROR (EidosInterpreter::Evaluate_Plus): the '+' operator requires that either (1) both operands have the same size(), or (2) one operand has size() == 1." << EidosTerminate(operator_token); } } + else if ((first_child_type == EidosValueType::kValueLogical) && (second_child_type == EidosValueType::kValueLogical)) + { + // both operands are logical, so we are computing an integer result + if (first_child_count == second_child_count) + { + const eidos_logical_t *first_child_data = first_child_value->LogicalData(); + const eidos_logical_t *second_child_data = second_child_value->LogicalData(); + EidosValue_Int_SP int_result_SP = EidosValue_Int_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int()); + EidosValue_Int *int_result = int_result_SP->resize_no_initialize(first_child_count); + + for (int value_index = 0; value_index < first_child_count; ++value_index) + int_result->set_int_no_check(first_child_data[value_index] + second_child_data[value_index], value_index); + + result_SP = int_result_SP; + } + else if (first_child_count == 1) + { + eidos_logical_t singleton_logical = first_child_value->LogicalAtIndex_NOCAST(0, operator_token); + const eidos_logical_t *second_child_data = second_child_value->LogicalData(); + EidosValue_Int_SP int_result_SP = EidosValue_Int_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int()); + EidosValue_Int *int_result = int_result_SP->resize_no_initialize(second_child_count); + + for (int value_index = 0; value_index < second_child_count; ++value_index) + int_result->set_int_no_check(singleton_logical + second_child_data[value_index], value_index); + + result_SP = int_result_SP; + } + else if (second_child_count == 1) + { + const eidos_logical_t *first_child_data = first_child_value->LogicalData(); + eidos_logical_t singleton_logical = second_child_value->LogicalAtIndex_NOCAST(0, operator_token); + EidosValue_Int_SP int_result_SP = EidosValue_Int_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int()); + EidosValue_Int *int_result = int_result_SP->resize_no_initialize(first_child_count); + + for (int value_index = 0; value_index < first_child_count; ++value_index) + int_result->set_int_no_check(first_child_data[value_index] + singleton_logical, value_index); + + result_SP = int_result_SP; + } + else // if ((first_child_count != second_child_count) && (first_child_count != 1) && (second_child_count != 1)) + { + EIDOS_TERMINATION << "ERROR (EidosInterpreter::Evaluate_Plus): the '+' operator requires that either (1) both operands have the same size(), or (2) one operand has size() == 1." << EidosTerminate(operator_token); + } + } + else if ((first_child_type == EidosValueType::kValueInt) && (second_child_type == EidosValueType::kValueLogical)) + { + // integer + logical -> integer result; we will gloss over the possibility of overflow here since it is so remote + if (first_child_count == second_child_count) + { + const int64_t *first_child_data = first_child_value->IntData(); + const eidos_logical_t *second_child_data = second_child_value->LogicalData(); + EidosValue_Int_SP int_result_SP = EidosValue_Int_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int()); + EidosValue_Int *int_result = int_result_SP->resize_no_initialize(first_child_count); + + for (int value_index = 0; value_index < first_child_count; ++value_index) + int_result->set_int_no_check(first_child_data[value_index] + second_child_data[value_index], value_index); + + result_SP = int_result_SP; + } + else if (first_child_count == 1) + { + int64_t singleton_integer = first_child_value->IntAtIndex_NOCAST(0, operator_token); + const eidos_logical_t *second_child_data = second_child_value->LogicalData(); + EidosValue_Int_SP int_result_SP = EidosValue_Int_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int()); + EidosValue_Int *int_result = int_result_SP->resize_no_initialize(second_child_count); + + for (int value_index = 0; value_index < second_child_count; ++value_index) + int_result->set_int_no_check(singleton_integer + second_child_data[value_index], value_index); + + result_SP = int_result_SP; + } + else if (second_child_count == 1) + { + const int64_t *first_child_data = first_child_value->IntData(); + eidos_logical_t singleton_logical = second_child_value->LogicalAtIndex_NOCAST(0, operator_token); + EidosValue_Int_SP int_result_SP = EidosValue_Int_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int()); + EidosValue_Int *int_result = int_result_SP->resize_no_initialize(first_child_count); + + for (int value_index = 0; value_index < first_child_count; ++value_index) + int_result->set_int_no_check(first_child_data[value_index] + singleton_logical, value_index); + + result_SP = int_result_SP; + } + else // if ((first_child_count != second_child_count) && (first_child_count != 1) && (second_child_count != 1)) + { + EIDOS_TERMINATION << "ERROR (EidosInterpreter::Evaluate_Plus): the '+' operator requires that either (1) both operands have the same size(), or (2) one operand has size() == 1." << EidosTerminate(operator_token); + } + } + else if ((first_child_type == EidosValueType::kValueLogical) && (second_child_type == EidosValueType::kValueInt)) + { + // logical + integer -> integer result; we will gloss over the possibility of overflow here since it is so remote + if (first_child_count == second_child_count) + { + const eidos_logical_t *first_child_data = first_child_value->LogicalData(); + const int64_t *second_child_data = second_child_value->IntData(); + EidosValue_Int_SP int_result_SP = EidosValue_Int_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int()); + EidosValue_Int *int_result = int_result_SP->resize_no_initialize(first_child_count); + + for (int value_index = 0; value_index < first_child_count; ++value_index) + int_result->set_int_no_check(first_child_data[value_index] + second_child_data[value_index], value_index); + + result_SP = int_result_SP; + } + else if (first_child_count == 1) + { + eidos_logical_t singleton_logical = first_child_value->LogicalAtIndex_NOCAST(0, operator_token); + const int64_t *second_child_data = second_child_value->IntData(); + EidosValue_Int_SP int_result_SP = EidosValue_Int_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int()); + EidosValue_Int *int_result = int_result_SP->resize_no_initialize(second_child_count); + + for (int value_index = 0; value_index < second_child_count; ++value_index) + int_result->set_int_no_check(singleton_logical + second_child_data[value_index], value_index); + + result_SP = int_result_SP; + } + else if (second_child_count == 1) + { + const eidos_logical_t *first_child_data = first_child_value->LogicalData(); + int64_t singleton_logical = second_child_value->IntAtIndex_NOCAST(0, operator_token); + EidosValue_Int_SP int_result_SP = EidosValue_Int_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int()); + EidosValue_Int *int_result = int_result_SP->resize_no_initialize(first_child_count); + + for (int value_index = 0; value_index < first_child_count; ++value_index) + int_result->set_int_no_check(first_child_data[value_index] + singleton_logical, value_index); + + result_SP = int_result_SP; + } + else // if ((first_child_count != second_child_count) && (first_child_count != 1) && (second_child_count != 1)) + { + EIDOS_TERMINATION << "ERROR (EidosInterpreter::Evaluate_Plus): the '+' operator requires that either (1) both operands have the same size(), or (2) one operand has size() == 1." << EidosTerminate(operator_token); + } + } else { if (((first_child_type != EidosValueType::kValueInt) && (first_child_type != EidosValueType::kValueFloat)) || ((second_child_type != EidosValueType::kValueInt) && (second_child_type != EidosValueType::kValueFloat))) diff --git a/eidos/eidos_test_operators_arithmetic.cpp b/eidos/eidos_test_operators_arithmetic.cpp index a3440bf7..6466ec97 100644 --- a/eidos/eidos_test_operators_arithmetic.cpp +++ b/eidos/eidos_test_operators_arithmetic.cpp @@ -69,9 +69,20 @@ void _RunOperatorPlusTests1(void) EidosAssertScriptRaise("c('bar', 'baz')+c('foo', 'biz', 'boz');", 15, "operator requires that either"); EidosAssertScriptSuccess_SV("c('bar', 'baz')+T;", {"barT", "bazT"}); EidosAssertScriptSuccess_SV("F+c('bar', 'baz');", {"Fbar", "Fbaz"}); - EidosAssertScriptRaise("T+F;", 1, "combination of operand types"); - EidosAssertScriptRaise("T+T;", 1, "combination of operand types"); - EidosAssertScriptRaise("F+F;", 1, "combination of operand types"); + EidosAssertScriptSuccess_I("T+F;", 1); + EidosAssertScriptSuccess_I("T+T;", 2); + EidosAssertScriptSuccess_I("F+F;", 0); + EidosAssertScriptSuccess_IV("c(T,F,T,T,F) + T;", {2, 1, 2, 2, 1}); + EidosAssertScriptSuccess_IV("F + c(T,F,T,T,F);", {1, 0, 1, 1, 0}); + EidosAssertScriptSuccess_IV("c(F,F,T,F,T) + c(T,F,T,T,F);", {1, 0, 2, 1, 1}); + EidosAssertScriptSuccess_I("T+5;", 6); + EidosAssertScriptSuccess_I("6+T;", 7); + EidosAssertScriptSuccess_I("F+8;", 8); + EidosAssertScriptSuccess_IV("c(T,F,T,T,F) + 5;", {6, 5, 6, 6, 5}); + EidosAssertScriptSuccess_IV("6 + c(T,F,T,T,F);", {7, 6, 7, 7, 6}); + EidosAssertScriptSuccess_IV("c(F,F,T,F,T) + c(1,2,3,4,5);", {1, 2, 4, 4, 6}); + EidosAssertScriptSuccess_IV("c(1,2,3,4,5) + c(F,F,T,F,T);", {1, 2, 4, 4, 6}); + EidosAssertScriptSuccess_IV("c(T,F,T,F,T) + c(T,F,T,T,F) + c(T,F,F,F,T);", {3, 0, 2, 1, 2}); EidosAssertScriptSuccess_I("+5;", 5); EidosAssertScriptSuccess_F("+5.0;", 5); EidosAssertScriptRaise("+'foo';", 0, "is not supported by"); From f52dc4f29dca728293ed93371246b5ff474c18d1 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 3 Jan 2026 14:14:53 -0600 Subject: [PATCH 063/107] make some stochastic self-tests a bit less stringent --- core/slim_test.cpp | 2 +- core/slim_test_genetics.cpp | 26 +++++++++++++------------- eidos/eidos_test_builtins.h | 14 +++++++------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/slim_test.cpp b/core/slim_test.cpp index d7fa2f3b..aa99873a 100644 --- a/core/slim_test.cpp +++ b/core/slim_test.cpp @@ -361,7 +361,7 @@ std::string gen1_setup("initialize() { initializeMutationRate(1e-7); initializeM std::string gen1_setup_sex("initialize() { initializeSex('X'); initializeMutationRate(1e-7); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } "); std::string gen2_stop(" 2 early() { stop(); } "); std::string gen1_setup_highmut_p1("initialize() { initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } "); -std::string gen1_setup_fixmut_p1("initialize() { initializeMutationRate(1e-4); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } 10 early() { sim.mutations[0].setEffectForTrait(0, 500.0); sim.recalculateFitness(); } "); +std::string gen1_setup_fixmut_p1("initialize() { initializeMutationRate(1e-4); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 5); } 10 early() { sim.mutations[0].setEffectForTrait(0, 500.0); sim.recalculateFitness(); } "); std::string gen1_setup_i1("initialize() { initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeInteractionType('i1', ''); } 1 early() { sim.addSubpop('p1', 10); } 1:10 late() { } "); std::string gen1_setup_i1x("initialize() { initializeSLiMOptions(dimensionality='x'); initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeInteractionType('i1', 'x'); } 1 early() { sim.addSubpop('p1', 10); } 1:10 late() { p1.individuals.x = runif(10); } "); std::string gen1_setup_i1xPx("initialize() { initializeSLiMOptions(dimensionality='x', periodicity='x'); initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeInteractionType('i1', 'x'); } 1 early() { sim.addSubpop('p1', 10); } 1:10 late() { p1.individuals.x = runif(10); } "); diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 92d7f733..9d7a3b7c 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -682,19 +682,19 @@ void _RunSubstitutionTests(void) // // Test Substitution properties - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { if (size(sim.substitutions) > 0) stop(); }", __LINE__); // check that our script generates substitutions fast enough - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; if (sub.fixationTick > 0 & sub.fixationTick <= 30) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; if (sub.mutationType == m1) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; if (sub.originTick > 0 & sub.originTick <= 10) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; if (sub.position >= 0 & sub.position <= 99999) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { if (sum(sim.substitutions.effect == 500.0) == 1) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; if (sub.subpopID == 1) stop(); }", __LINE__); - SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.fixationTick = 10; stop(); }", "read-only property", __LINE__); - SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.mutationType = m1; stop(); }", "read-only property", __LINE__); - SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.originTick = 10; stop(); }", "read-only property", __LINE__); - SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.position = 99999; stop(); }", "read-only property", __LINE__); - SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.effect = 50.0; stop(); }", "read-only property", __LINE__); - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "30 early() { sub = sim.substitutions[0]; sub.subpopID = 237; if (sub.subpopID == 237) stop(); }", __LINE__); // legal; this field may be used as a user tag + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { if (size(sim.substitutions) > 0) stop(); }", __LINE__); // check that our script generates substitutions fast enough + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; if (sub.fixationTick > 0 & sub.fixationTick <= 30) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; if (sub.mutationType == m1) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; if (sub.originTick > 0 & sub.originTick <= 10) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; if (sub.position >= 0 & sub.position <= 99999) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { if (sum(sim.substitutions.effect == 500.0) == 1) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; if (sub.subpopID == 1) stop(); }", __LINE__); + SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.fixationTick = 10; stop(); }", "read-only property", __LINE__); + SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.mutationType = m1; stop(); }", "read-only property", __LINE__); + SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.originTick = 10; stop(); }", "read-only property", __LINE__); + SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.position = 99999; stop(); }", "read-only property", __LINE__); + SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.effect = 50.0; stop(); }", "read-only property", __LINE__); + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.subpopID = 237; if (sub.subpopID == 237) stop(); }", __LINE__); // legal; this field may be used as a user tag } #pragma mark Haplosome tests diff --git a/eidos/eidos_test_builtins.h b/eidos/eidos_test_builtins.h index 8707e2f3..0ea4f6f5 100644 --- a/eidos/eidos_test_builtins.h +++ b/eidos/eidos_test_builtins.h @@ -1120,13 +1120,13 @@ if (any(abs(x - 10000) > 500)) stop('Mismatch in expectation vs. realization of setSeed(asInteger(clock() * 100000)); m = mean(rbeta(10000, 2, 3)); // expectation is 0.4 -if (abs(m - 0.4) > 0.008) stop('Mismatch in expectation vs. realization of rbeta() - could be random chance (but very unlikely), rerun test'); +if (abs(m - 0.4) > 0.010) stop('Mismatch in expectation vs. realization of rbeta() - could be random chance (but very unlikely), rerun test'); // *********************************************************************************************** setSeed(asInteger(clock() * 100000)); m = mean(rexp(100000, 5)); // expectation is 5 -if (abs(m - 5) > 0.07) stop('Mismatch in expectation vs. realization of rexp() - could be random chance (but very unlikely), rerun test'); +if (abs(m - 5) > 0.08) stop('Mismatch in expectation vs. realization of rexp() - could be random chance (but very unlikely), rerun test'); // *********************************************************************************************** @@ -1144,7 +1144,7 @@ if (abs(m - 5) > 0.4) stop('Mismatch in expectation vs. realization of rgamma() setSeed(asInteger(clock() * 100000)); m = mean(rgeom(10000, 0.1)); // expectation is 9 -if (abs(m - 9) > 0.4) stop('Mismatch in expectation vs. realization of rgeom() - could be random chance (but very unlikely), rerun test'); +if (abs(m - 9) > 0.5) stop('Mismatch in expectation vs. realization of rgeom() - could be random chance (but very unlikely), rerun test'); // *********************************************************************************************** @@ -1167,7 +1167,7 @@ if (abs(cov - 0.2) > 0.03) stop('Mismatch in expectation vs. realization of rmvn setSeed(asInteger(clock() * 100000)); m = mean(rnbinom(100000, 5, 0.3)); // expectation is (1-p)r/p, 0.7*5/0.3 == 11.66667, for our parameterization (different from Wikipedia) -if (abs(m - 11.66667) > 0.08) stop('Mismatch in expectation vs. realization of rnbinom() - could be random chance (but very unlikely), rerun test'); +if (abs(m - 11.66667) > 0.09) stop('Mismatch in expectation vs. realization of rnbinom() - could be random chance (but very unlikely), rerun test'); // *********************************************************************************************** @@ -1179,7 +1179,7 @@ if (abs(m - 5) > 0.02) stop('Mismatch in expectation vs. realization of rnorm() setSeed(asInteger(clock() * 100000)); m = mean(rpois(10000, 5)); // expectation is 5 -if (abs(m - 5) > 0.09) stop('Mismatch in expectation vs. realization of rpois() - could be random chance (but very unlikely), rerun test'); +if (abs(m - 5) > 0.11) stop('Mismatch in expectation vs. realization of rpois() - could be random chance (but very unlikely), rerun test'); // *********************************************************************************************** @@ -1192,7 +1192,7 @@ if (any(abs(x - 10000) > 500)) stop('Mismatch in expectation vs. realization of setSeed(asInteger(clock() * 100000)); m = mean(rweibull(10000, 5, 0.5)); // expectation is lambda * Gamma(1 + 1/k), and for positive integers Gamma(x) = (x-1)!, so here it is 5 * 2 -if (abs(m - (5 * 2)) > 1.0) stop('Mismatch in expectation vs. realization of rweibull() - could be random chance (but very unlikely), rerun test'); +if (abs(m - (5 * 2)) > 1.1) stop('Mismatch in expectation vs. realization of rweibull() - could be random chance (but very unlikely), rerun test'); // *********************************************************************************************** @@ -1206,7 +1206,7 @@ if (any(abs(x - 10000) > 500)) stop('Mismatch in expectation vs. realization of setSeed(asInteger(clock() * 100000)); m = mean(rztpois(10000, 3)); expected = 3 / (1 - exp(-3)); // ~= 3.15719 -if (abs(m - expected) > 0.07) stop('Mismatch in expectation vs. realization of rztpois() - could be random chance (but very unlikely), rerun test'); +if (abs(m - expected) > 0.08) stop('Mismatch in expectation vs. realization of rztpois() - could be random chance (but very unlikely), rerun test'); )V0G0N" From a22b62b5965af79f3d3bfea03e20a4ee364abf08 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 3 Jan 2026 15:16:43 -0600 Subject: [PATCH 064/107] add zygosityOfMutations() method to Individual --- QtSLiM/help/SLiMHelpClasses.html | 9 +- SLiMgui/SLiMHelpClasses.rtf | 57 ++++- VERSIONS | 1 + core/chromosome.h | 2 + core/haplosome.h | 6 +- core/individual.cpp | 365 +++++++++++++++++++++++++++++++ core/individual.h | 5 +- core/slim_globals.cpp | 1 + core/slim_globals.h | 2 + core/slim_test_genetics.cpp | 255 +++++++++++++++++++++ 10 files changed, 696 insertions(+), 7 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 3cfb2246..1d3925ec 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -474,7 +474,7 @@

The mutType parameter may be NULL, or may specify a mutation type by its integer id or with the MutationType object itself.  If mutType is NULL (the default), mutations of every mutation type are returned; no filtering by mutation type is done.  Otherwise, only mutations of the specified mutation type will be returned.

The chromosomes parameter may be NULL, or may provide a vector of chromosomes specified by their integer id, string symbol, or with the Chromosome object itself.  If chromosomes is NULL (the default), mutations associated with every chromosome are returned; no filtering by chromosome is done.  Otherwise, only mutations associated with the specified chromosomes will be returned.

The returned vector will contain tranches of mutations, one tranche per chromosome, in the order that the chromosomes were specified (if chromosomes is non-NULL) or the order the chromosomes were defined in the model (if chromosomes is NULL).  Within a given tranche, the mutations for that chromosome will be returned in sorted order by position.  (If more than one mutation associated with a given chromosome exists at the same position, the order in which those mutations are returned is undefined.)

-

This method replaces the deprecated method uniqueMutationsOfType(), while providing additional useful options.  It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.

+

This method replaces the deprecated method uniqueMutationsOfType(), while providing additional useful options.  It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.  See also the method zygosityOfMutations(), which provides an alternative approach for assessing the zygosity of mutations in an individual.

– (float)offsetForTrait([Niso<Trait> trait = NULL])

Returns the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

– (float)phenotypeForTrait([Niso<Trait> trait = NULL])

@@ -534,6 +534,13 @@

 (object<Mutation>)uniqueMutationsOfType(io<MutationType>$ mutType)

This method has been deprecated, and may be removed in a future release of SLiM.  Its functionality was replaced by mutationsFromHaplosomes() in SLiM 5.0.

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations in the individual.  Mutations present in both homologous haplosomes will occur only once in the result of this method, and the mutations for a given chromosomes will be given in sorted order by position, so in single-chromosome simulations this method is similar to sortBy(unique(individual.haplosomes.mutationsOfType(mutType)), "position").  (Even with a single chromosome it is not identical to that call, since if multiple mutations exist at the exact same position, they may be sorted differently by this method than they would be by sortBy().)  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType().  This method is provided for speed; it is much faster than the corresponding Eidos code.  Indeed, it is faster than just individual.haplosomes.mutationsOfType(mutType), and gives uniquing and sorting on top of that, so it is advantageous unless duplicate entries for homozygous mutations are actually needed.

+

+ (integer)zygosityOfMutations([No<Mutation> mutations = NULL], [integer$ hemizygousValue = 1], [integer$ haploidValue = 1])

+

Returns an integer matrix with the target individuals’ zygosity for all of the Mutation objects passed in mutations.  If the optional mutations argument is NULL (the default), zygosity values will be returned for all of the active Mutation objects in the species – the same Mutation objects, and in the same order, as would be returned by the mutations property of sim, in other words.  The returned matrix has one column for each individual and one row for each mutation.

+

For a mutation on a diploid chromosome, the zygosity will be either 0 (absent), 1 (heterozygous), or 2 (homozygous).  This is the straightforward “base case” that is usually meant by the term “zygosity”.

+

If one of the two haplosomes of an intrinsically diploid chromosome is a null haplosome (as would be the case for an X chromosome in a male individual, for example), mutations present in the non-null haplosome are called “hemizygous”, and the zygosity returned for them is configurable using the hemizygousValue parameter.  By default, the zygosity returned for hemizygous mutations is 1.

+

Finally, although the term “zygosity” is not usually used in the context of haploidy, this method nevertheless supports intrinsically haploid chromosomes; the zygosity returned for mutations present on an intrinsically haploid chromosome is configurable using the haploidValue parameter.  By default, the zygosity returned for haploid mutations is 1.

+

For a large number of mutations – and especially for a mutations value of NULL, representing all mutations in the species – this method should be much more efficient than using methods such as containsMutations() to assess zygosity.  Nevertheless, calculating fitness effects in script based upon zygosity will generally be slower than SLiM’s internal fitness calculations.  Note that for just one or a few mutations – especially if mutations contains just a small fraction of all of the mutations in the species – this method will probably be much slower than alternative approaches; this method is optimized for the bulk case.

+

See also the method mutationsFromHaplosomes(), which provides an alternative approach for assessing the zygosity of mutations in an individual.

5.8  Class InteractionType

5.8.1  InteractionType properties

id => (integer$)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 442dca96..dad35f84 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -3873,7 +3873,9 @@ The returned vector will contain tranches of mutations, one tranche per chromoso \f4\fs20 . (If more than one mutation associated with a given chromosome exists at the same position, the order in which those mutations are returned is undefined.)\ This method replaces the deprecated method \f3\fs18 uniqueMutationsOfType() -\f4\fs20 , while providing additional useful options. It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.\ +\f4\fs20 , while providing additional useful options. It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently. See also the method +\f3\fs18 zygosityOfMutations() +\f4\fs20 , which provides an alternative approach for assessing the zygosity of mutations in an individual.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(float)offsetForTrait([Niso\'a0trait\'a0=\'a0NULL])\ @@ -4555,6 +4557,59 @@ Historically, this method was often useful in models that used a particular muta \f4\fs20 . This method is provided for speed; it is much faster than the corresponding Eidos code. Indeed, it is faster than just \f3\fs18 individual.haplosomes.mutationsOfType(mutType) \f4\fs20 , and gives uniquing and sorting on top of that, so it is advantageous unless duplicate entries for homozygous mutations are actually needed.\ +\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 +\'a0(integer)zygosityOfMutations([No\'a0mutations\'a0=\'a0NULL], [integer$\'a0hemizygousValue\'a0=\'a01], [integer$\'a0haploidValue\'a0=\'a01])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns an +\f3\fs18 integer +\f4\fs20 matrix with the target individuals\'92 zygosity for all of the +\f3\fs18 Mutation +\f4\fs20 objects passed in +\f3\fs18 mutations +\f4\fs20 . If the optional +\f3\fs18 mutations +\f4\fs20 argument is +\f3\fs18 NULL +\f4\fs20 (the default), zygosity values will be returned for all of the active +\f3\fs18 Mutation +\f4\fs20 objects in the species \'96 the same +\f3\fs18 Mutation +\f4\fs20 objects, and in the same order, as would be returned by the +\f3\fs18 mutations +\f4\fs20 property of +\f3\fs18 sim +\f4\fs20 , in other words. The returned matrix has one column for each individual and one row for each mutation.\ +For a mutation on a diploid chromosome, the zygosity will be either +\f3\fs18 0 +\f4\fs20 (absent), +\f3\fs18 1 +\f4\fs20 (heterozygous), or +\f3\fs18 2 +\f4\fs20 (homozygous). This is the straightforward \'93base case\'94 that is usually meant by the term \'93zygosity\'94.\ +If one of the two haplosomes of an intrinsically diploid chromosome is a null haplosome (as would be the case for an X chromosome in a male individual, for example), mutations present in the non-null haplosome are called \'93hemizygous\'94, and the zygosity returned for them is configurable using the +\f3\fs18 hemizygousValue +\f4\fs20 parameter. By default, the zygosity returned for hemizygous mutations is +\f3\fs18 1 +\f4\fs20 .\ +Finally, although the term \'93zygosity\'94 is not usually used in the context of haploidy, this method nevertheless supports intrinsically haploid chromosomes; the zygosity returned for mutations present on an intrinsically haploid chromosome is configurable using the +\f3\fs18 haploidValue +\f4\fs20 parameter. By default, the zygosity returned for haploid mutations is +\f3\fs18 1 +\f4\fs20 .\ +For a large number of mutations \'96 and especially for a +\f3\fs18 mutations +\f4\fs20 value of +\f3\fs18 NULL +\f4\fs20 , representing all mutations in the species \'96 this method should be much more efficient than using methods such as +\f3\fs18 containsMutations() +\f4\fs20 to assess zygosity. Nevertheless, calculating fitness effects in script based upon zygosity will generally be slower than SLiM\'92s internal fitness calculations. Note that for just one or a few mutations \'96 especially if +\f3\fs18 mutations +\f4\fs20 contains just a small fraction of all of the mutations in the species \'96 this method will probably be much slower than alternative approaches; this method is optimized for the bulk case.\ +See also the method +\f3\fs18 mutationsFromHaplosomes() +\f4\fs20 , which provides an alternative approach for assessing the zygosity of mutations in an individual.\ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 \f0\b\fs22 \cf0 5.8 Class InteractionType\ diff --git a/VERSIONS b/VERSIONS index 33ef6c5e..fe0ce406 100644 --- a/VERSIONS +++ b/VERSIONS @@ -132,6 +132,7 @@ multitrait branch: add a cachedFitness property to class Individual to provide another way of accessing fitness values for individuals, besides the cachedFitness() method of Subpopulation extend the Eidos deleteFile() and fileExists() functions to support passing in a vector of file paths extend the Eidos + operator to support adding logical+logical, integer+logical, and logical+integer (all for NxN, 1xN, Nx1), as in R + add zygosityOfMutations([No mutations = NULL], [integer$ hemizygousValue = 1], [integer$ haploidValue = 1]) for assessing zygosity (see also mutationsFromHaplosomes()) version 5.1 (Eidos version 4.1): diff --git a/core/chromosome.h b/core/chromosome.h index 8e102fe7..67fbbcf8 100644 --- a/core/chromosome.h +++ b/core/chromosome.h @@ -276,6 +276,8 @@ class Chromosome : public EidosDictionaryRetained // private scratch space for the use of Population::RemoveAllFixedMutations() std::vector fixed_mutation_accumulator_; + int8_t scratch_; // temporary scratch space for use by algorithms; regard as volatile outside your own code block + // a user-defined tag value slim_usertag_t tag_value_ = SLIM_TAG_UNSET_VALUE; diff --git a/core/haplosome.h b/core/haplosome.h index 7bc7801d..02ddb05a 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -429,9 +429,9 @@ class Haplosome : public EidosObject virtual void SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) override; virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; - static EidosValue_SP ExecuteMethod_Accelerated_containsMarkerMutation(EidosObject **p_values, size_t p_values_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - static EidosValue_SP ExecuteMethod_Accelerated_containsMutations(EidosObject **p_values, size_t p_values_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - static EidosValue_SP ExecuteMethod_Accelerated_countOfMutationsOfType(EidosObject **p_values, size_t p_values_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + static EidosValue_SP ExecuteMethod_Accelerated_containsMarkerMutation(EidosObject **p_elements, size_t p_elements_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + static EidosValue_SP ExecuteMethod_Accelerated_containsMutations(EidosObject **p_elements, size_t p_elements_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + static EidosValue_SP ExecuteMethod_Accelerated_countOfMutationsOfType(EidosObject **p_elements, size_t p_elements_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_mutationsOfType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_nucleotides(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_positionsOfMutationsOfType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); diff --git a/core/individual.cpp b/core/individual.cpp index 79718489..642c3318 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -4390,6 +4390,7 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddIntStringObject_OSN("trait", gSLiM_Trait_Class, gStaticEidosValueNULL))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_uniqueMutationsOfType, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->MarkDeprecated()); + methods->emplace_back(((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_zygosityOfMutations, kEidosValueMaskInt))->AddObject_ON("mutations", gSLiM_Mutation_Class, gStaticEidosValueNULL)->AddInt_OS("hemizygousValue", gStaticEidosValue_Integer1)->AddInt_OS("haploidValue", gStaticEidosValue_Integer1))); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_mutationsFromHaplosomes, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddString_S("category")->AddIntObject_OSN("mutType", gSLiM_MutationType_Class, gStaticEidosValueNULL)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_outputIndividuals, kEidosValueMaskVOID))->AddString_OSN(gEidosStr_filePath, gStaticEidosValueNULL)->AddLogical_OS("append", gStaticEidosValue_LogicalF)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, "chromosome", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddLogical_OS("spatialPositions", gStaticEidosValue_LogicalT)->AddLogical_OS("ages", gStaticEidosValue_LogicalT)->AddLogical_OS("ancestralNucleotides", gStaticEidosValue_LogicalF)->AddLogical_OS("pedigreeIDs", gStaticEidosValue_LogicalF)->AddLogical_OS("objectTags", gStaticEidosValue_LogicalF)); @@ -4414,6 +4415,7 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ case gID_outputIndividualsToVCF: return ExecuteMethod_outputIndividualsToVCF(p_method_id, p_target, p_arguments, p_interpreter); case gID_readIndividualsFromVCF: return ExecuteMethod_readIndividualsFromVCF(p_method_id, p_target, p_arguments, p_interpreter); case gID_setSpatialPosition: return ExecuteMethod_setSpatialPosition(p_method_id, p_target, p_arguments, p_interpreter); + case gID_zygosityOfMutations: return ExecuteMethod_zygosityOfMutations(p_method_id, p_target, p_arguments, p_interpreter); default: { // In a sense, we here "subclass" EidosDictionaryUnretained_Class to override setValuesVectorized(); we set a flag remembering that @@ -5993,6 +5995,369 @@ EidosValue_SP Individual_Class::ExecuteMethod_setSpatialPosition(EidosGlobalStri return gStaticEidosValueVOID; } +// ********************* + (integer)zygosityOfMutations([No mutations = NULL], [integer$ hemizygousValue = 1], [integer$ haploidValue = 1]) +// +EidosValue_SP Individual_Class::ExecuteMethod_zygosityOfMutations(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + Individual **target_individuals = (Individual **)p_target->data(); + int target_size = p_target->Count(); + + if (target_size == 0) + return gStaticEidosValue_Integer_ZeroVec; + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForIndividualsVector(target_individuals, target_size); + + if (species == nullptr) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_zygosityOfMutations): zygosityOfMutations() requires that all target individuals belong to the same species." << EidosTerminate(); + + species->population_.CheckForDeferralInIndividualsVector(target_individuals, target_size, "Individual_Class::ExecuteMethod_zygosityOfMutations"); + + const std::vector &chromosomes = species->Chromosomes(); + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + + EidosValue *mutations_value = p_arguments[0].get(); + EidosValue *hemizygousValue_value = p_arguments[1].get(); + EidosValue *haploidValue_value = p_arguments[2].get(); + + int64_t hemizygousValue = hemizygousValue_value->IntAtIndex_NOCAST(0, nullptr); + int64_t haploidValue = haploidValue_value->IntAtIndex_NOCAST(0, nullptr); + std::vector focalMutations; + + if (mutations_value->Type() == EidosValueType::kValueNULL) + { + // When assessing all mutations, we assume that all chromosomes need to be scanned + int registry_size; + const MutationIndex *registry = species->population_.MutationRegistry(®istry_size); + + focalMutations.resize(registry_size); + + Mutation **focalMutations_data = focalMutations.data(); + + for (int registry_index = 0; registry_index < registry_size; ++registry_index) + { + Mutation *mut = mut_block_ptr + registry[registry_index]; + + focalMutations_data[registry_index] = mut; + } + + // mark a scratch value inside all chromosomes; 1 indicates the chromosome is active + for (Chromosome *chromosome : chromosomes) + chromosome->scratch_ = 1; + } + else + { + // When assessing a vector of mutations, we first determine which chromosomes we need to scan + // In this case we also need to check that all mutations belong to the same species as the individuals + if (Community::SpeciesForMutations(mutations_value) != species) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_zygosityOfMutations): zygosityOfMutations() requires that all mutations belong to the same species as the target individuals." << EidosTerminate(); + + // zero out the scratch_ for all chromosomes; 0 indicates the chromosome is not active + for (Chromosome *chromosome : chromosomes) + chromosome->scratch_ = 0; + + Mutation **mutations_data = (Mutation **)mutations_value->ObjectData(); + int mutations_count = mutations_value->Count(); + + focalMutations.resize(mutations_count); + + Mutation **focalMutations_data = focalMutations.data(); + + for (int mutations_index = 0; mutations_index < mutations_count; ++mutations_index) + { + Mutation *mut = mutations_data[mutations_index]; + + focalMutations_data[mutations_index] = mut; + + // mark a scratch value inside the associated chromosome; 1 indicates the chromosome is active + chromosomes[mut->chromosome_index_]->scratch_ = 1; + } + } + + if (focalMutations.size() == 0) + return gStaticEidosValue_Integer_ZeroVec; + + // allocate the result vector + EidosValue_Int *integer_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(focalMutations.size() * target_size); + int64_t *integer_result_data = integer_result->data_mutable(); + + // tabulate the results for each individuals + const int8_t PRESENT_HETEROZYGOUS = 1; + const int8_t PRESENT_HOMOZYGOUS = 2; + const int8_t PRESENT_HEMIZYGOUS = 3; + const int8_t PRESENT_HAPLOID = 4; + + for (int target_index = 0; target_index < target_size; ++target_index) + { + Individual *individual = target_individuals[target_index]; + + // first we zero out our zygosity counters; note that if this is parallelized, it will need separate scratch space for each thread + for (Mutation *mut : focalMutations) + mut->scratch_ = 0; + + // loop over the active chromosomes + for (Chromosome *chromosome : chromosomes) + { + if (chromosome->scratch_ == 0) + continue; + + unsigned int chromosome_index = chromosome->Index(); + + if (chromosome->IntrinsicPloidy() == 2) + { + // intrinsically diploid case + Haplosome *haplosome1 = individual->haplosomes_[species->FirstHaplosomeIndices()[chromosome_index]]; + Haplosome *haplosome2 = individual->haplosomes_[species->LastHaplosomeIndices()[chromosome_index]]; + bool haplosome1_isnull = haplosome1->IsNull(); + bool haplosome2_isnull = haplosome2->IsNull(); + + if (haplosome1_isnull && haplosome2_isnull) + continue; + + if (haplosome1_isnull || haplosome2_isnull) + { + // hemizygous case + Haplosome *haplosome = (haplosome1_isnull ? haplosome2 : haplosome1); + const int32_t mutrun_count = haplosome->mutrun_count_; + + for (int run_index = 0; run_index < mutrun_count; ++run_index) + { + const MutationRun *mutrun = haplosome->mutruns_[run_index]; + const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); + const MutationIndex *haplosome_max = mutrun->end_pointer_const(); + + while (haplosome_iter != haplosome_max) + { + MutationIndex haplosome_mutation = *haplosome_iter++; + Mutation *mutation = (mut_block_ptr + haplosome_mutation); + + mutation->scratch_ = PRESENT_HEMIZYGOUS; + } + } + } + else + { + // diploid case + const int32_t mutrun_count = haplosome1->mutrun_count_; + + for (int run_index = 0; run_index < mutrun_count; ++run_index) + { + const MutationRun *mutrun1 = haplosome1->mutruns_[run_index]; + const MutationRun *mutrun2 = haplosome2->mutruns_[run_index]; + + const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); + const MutationIndex *haplosome2_iter = mutrun2->begin_pointer_const(); + + const MutationIndex *haplosome1_max = mutrun1->end_pointer_const(); + const MutationIndex *haplosome2_max = mutrun2->end_pointer_const(); + + if ((haplosome1_iter != haplosome1_max) && (haplosome2_iter != haplosome2_max)) + { + MutationIndex haplosome1_mutindex = *haplosome1_iter, haplosome2_mutindex = *haplosome2_iter; + slim_position_t haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_, haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + + do + { + if (haplosome1_iter_position < haplosome2_iter_position) + { + // Process a mutation in haplosome1 since it is leading + (mut_block_ptr + haplosome1_mutindex)->scratch_ = PRESENT_HETEROZYGOUS; + + if (++haplosome1_iter == haplosome1_max) + break; + else { + haplosome1_mutindex = *haplosome1_iter; + haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; + } + } + else if (haplosome1_iter_position > haplosome2_iter_position) + { + // Process a mutation in haplosome2 since it is leading + (mut_block_ptr + haplosome2_mutindex)->scratch_ = PRESENT_HETEROZYGOUS; + + if (++haplosome2_iter == haplosome2_max) + break; + else { + haplosome2_mutindex = *haplosome2_iter; + haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + } + } + else + { + // Look for homozygosity: haplosome1_iter_position == haplosome2_iter_position + slim_position_t position = haplosome1_iter_position; + const MutationIndex *haplosome1_start = haplosome1_iter; + + // advance through haplosome1 as long as we remain at the same position, handling one mutation at a time + do + { + const MutationIndex *haplosome2_matchscan = haplosome2_iter; + + // advance through haplosome2 with haplosome2_matchscan, looking for a match for the current mutation in haplosome1, to determine whether we are homozygous or not + while (haplosome2_matchscan != haplosome2_max && (mut_block_ptr + *haplosome2_matchscan)->position_ == position) + { + if (haplosome1_mutindex == *haplosome2_matchscan) + { + // a match was found, so we record a homozygous state + (mut_block_ptr + haplosome1_mutindex)->scratch_ = PRESENT_HOMOZYGOUS; + goto homozygousExit1; + } + + haplosome2_matchscan++; + } + + // no match was found, so we are heterozygous + (mut_block_ptr + haplosome1_mutindex)->scratch_ = PRESENT_HETEROZYGOUS; + + homozygousExit1: + + if (++haplosome1_iter == haplosome1_max) + break; + else { + haplosome1_mutindex = *haplosome1_iter; + haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; + } + } while (haplosome1_iter_position == position); + + // advance through haplosome2 as long as we remain at the same position, handling one mutation at a time + do + { + const MutationIndex *haplosome1_matchscan = haplosome1_start; + + // advance through haplosome1 with haplosome1_matchscan, looking for a match for the current mutation in haplosome2, to determine whether we are homozygous or not + while ((haplosome1_matchscan != haplosome1_max) && ((mut_block_ptr + *haplosome1_matchscan)->position_ == position)) + { + if (haplosome2_mutindex == *haplosome1_matchscan) + { + // a match was found; we know this match was already found by the haplosome1 loop above + goto homozygousExit2; + } + + haplosome1_matchscan++; + } + + // no match was found, so we are heterozygous + (mut_block_ptr + haplosome2_mutindex)->scratch_ = PRESENT_HETEROZYGOUS; + + homozygousExit2: + + if (++haplosome2_iter == haplosome2_max) + break; + else { + haplosome2_mutindex = *haplosome2_iter; + haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + } + } while (haplosome2_iter_position == position); + + // break out if either haplosome has reached its end + if (haplosome1_iter == haplosome1_max || haplosome2_iter == haplosome2_max) + break; + } + } while (true); + } + + // one or the other haplosome has now reached its end, so now we just need to handle the remaining mutations in the unfinished haplosome +#if DEBUG + assert(!(haplosome1_iter != haplosome1_max && haplosome2_iter != haplosome2_max)); +#endif + + // if haplosome1 is unfinished, finish it + while (haplosome1_iter != haplosome1_max) + { + MutationIndex haplosome1_mutindex = *haplosome1_iter++; + (mut_block_ptr + haplosome1_mutindex)->scratch_ = PRESENT_HETEROZYGOUS; + } + + // if haplosome2 is unfinished, finish it + while (haplosome2_iter != haplosome2_max) + { + MutationIndex haplosome2_mutindex = *haplosome2_iter++; + (mut_block_ptr + haplosome2_mutindex)->scratch_ = PRESENT_HETEROZYGOUS; + } + } + } + } + else + { + // intrinsically haploid case + Haplosome *haplosome = individual->haplosomes_[species->FirstHaplosomeIndices()[chromosome_index]]; + + if (haplosome->IsNull()) + continue; + + // haploid case + const int32_t mutrun_count = haplosome->mutrun_count_; + + for (int run_index = 0; run_index < mutrun_count; ++run_index) + { + const MutationRun *mutrun = haplosome->mutruns_[run_index]; + const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); + const MutationIndex *haplosome_max = mutrun->end_pointer_const(); + + while (haplosome_iter != haplosome_max) + { + MutationIndex haplosome_mutation = *haplosome_iter++; + Mutation *mutation = (mut_block_ptr + haplosome_mutation); + + mutation->scratch_ = PRESENT_HAPLOID; + } + } + } + } + + // then run through the mutations again and transfer counts (zygosity) to the result matrix + int64_t *result_column_ptr = integer_result_data + focalMutations.size() * target_index; + + if ((hemizygousValue == 1) && (haploidValue == 1)) + { + // simple occurrence counts + for (Mutation *mut : focalMutations) + { + int8_t scratch_value = mut->scratch_; + + // an occurrence count of 0 or 2 is unambiguous in the way it is recorded + // other occurrence counts all translate to a zygosity of 1 + if ((scratch_value == 0) || (scratch_value == 2)) + *(result_column_ptr++) = scratch_value; + else + *(result_column_ptr++) = 1; + } + } + else + { + // not simple occurrence counts + for (Mutation *mut : focalMutations) + { + int8_t scratch_value = mut->scratch_; + + // an occurrence count of 0 or 2 is unambiguous in the way it is recorded + if ((scratch_value == 0) || (scratch_value == 2)) + { + *(result_column_ptr++) = scratch_value; + continue; + } + + // other occurrence counts need to be translated to the correct result value + if (scratch_value == PRESENT_HEMIZYGOUS) + *(result_column_ptr++) = hemizygousValue; + else if (scratch_value == PRESENT_HAPLOID) + *(result_column_ptr++) = haploidValue; + else + *(result_column_ptr++) = 1; + } + } + } + + // set the dimensionality of the result matrix + const int64_t dim_buf[2] = {(int64_t)focalMutations.size(), target_size}; + + integer_result->SetDimensions(2, dim_buf); + + return EidosValue_SP(integer_result); +} + // // Phenotype demand diff --git a/core/individual.h b/core/individual.h index ea908a34..1806560e 100644 --- a/core/individual.h +++ b/core/individual.h @@ -340,13 +340,13 @@ class Individual : public EidosDictionaryUnretained virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; EidosValue_SP ExecuteMethod_containsMutations(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - static EidosValue_SP ExecuteMethod_Accelerated_countOfMutationsOfType(EidosObject **p_values, size_t p_values_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + static EidosValue_SP ExecuteMethod_Accelerated_countOfMutationsOfType(EidosObject **p_elements, size_t p_elements_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_haplosomesForChromosomes(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_offsetForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_relatedness(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_sharedParentCount(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - static EidosValue_SP ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosObject **p_values, size_t p_values_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + static EidosValue_SP ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosObject **p_elements, size_t p_elements_size, EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_uniqueMutationsOfType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_mutationsFromHaplosomes(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); @@ -441,6 +441,7 @@ class Individual_Class : public EidosDictionaryUnretained_Class EidosValue_SP ExecuteMethod_outputIndividualsToVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_readIndividualsFromVCF(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_setSpatialPosition(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + EidosValue_SP ExecuteMethod_zygosityOfMutations(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_demandPhenotype(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 19f1e5e4..fd32a59a 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1377,6 +1377,7 @@ const std::string &gStr_setSpatialPosition = EidosRegisteredString("setSpatialPo const std::string &gStr_substitutionsOfType = EidosRegisteredString("substitutionsOfType", gID_substitutionsOfType); const std::string &gStr_sumOfMutationsOfType = EidosRegisteredString("sumOfMutationsOfType", gID_sumOfMutationsOfType); const std::string &gStr_uniqueMutationsOfType = EidosRegisteredString("uniqueMutationsOfType", gID_uniqueMutationsOfType); +const std::string &gStr_zygosityOfMutations = EidosRegisteredString("zygosityOfMutations", gID_zygosityOfMutations); const std::string &gStr_mutationsFromHaplosomes = EidosRegisteredString("mutationsFromHaplosomes", gID_mutationsFromHaplosomes); const std::string &gStr_readHaplosomesFromMS = EidosRegisteredString("readHaplosomesFromMS", gID_readHaplosomesFromMS); const std::string &gStr_readHaplosomesFromVCF = EidosRegisteredString("readHaplosomesFromVCF", gID_readHaplosomesFromVCF); diff --git a/core/slim_globals.h b/core/slim_globals.h index 4a85795e..5ce49737 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -967,6 +967,7 @@ extern const std::string &gStr_setSpatialPosition; extern const std::string &gStr_substitutionsOfType; extern const std::string &gStr_sumOfMutationsOfType; extern const std::string &gStr_uniqueMutationsOfType; +extern const std::string &gStr_zygosityOfMutations; extern const std::string &gStr_mutationsFromHaplosomes; extern const std::string &gStr_readHaplosomesFromMS; extern const std::string &gStr_readHaplosomesFromVCF; @@ -1454,6 +1455,7 @@ enum _SLiMGlobalStringID : int { gID_substitutionsOfType, gID_sumOfMutationsOfType, gID_uniqueMutationsOfType, + gID_zygosityOfMutations, gID_mutationsFromHaplosomes, gID_readHaplosomesFromMS, gID_readHaplosomesFromVCF, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 9d7a3b7c..7bf8527b 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1336,6 +1336,261 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(cachedFitness3); + // test the new zygosityOfMutations() method + std::string test_zygosity1 = // simple one-chromosome diploid test + R"V0G0N( + initialize() { + initializeMutationRate(1e-7); + initializeMutationType("m1", 0.5, "n", 0.0, 0.01); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 9999999); + initializeRecombinationRate(1e-8); + } + 1 early() { + sim.addSubpop("p1", 10); + } + 20 late() { + inds = p1.individuals; + muts = sim.mutations; + + // calculate zygosity + z = inds.zygosityOfMutations(NULL); + + // cross-check row sums: occurrence counts of each mutation + mutCounts1 = rowSums(z); + mutCounts2 = sim.mutationCounts(p1, NULL); + if (!identical(mutCounts1, mutCounts2)) + stop("individual mutation counts do not match"); + + // cross-check column sums: the number of mutations per individual + indCounts1 = colSums(z); + indCounts2 = sapply(inds, "applyValue.haplosomes.mutations.size();"); + if (!identical(indCounts1, indCounts2)) + stop("individual mutation counts do not match"); + + // cross-check against a zygosity matrix from containsMutations() + m = NULL; + for (ind in inds) + { + counts_h0 = ind.haplosomes[0].containsMutations(muts); + counts_h1 = ind.haplosomes[1].containsMutations(muts); + zygosity = counts_h0 + counts_h1; + m = cbind(m, zygosity); + } + if (!identical(z, m)) + stop("zygosity matrices do not match"); + } + )V0G0N"; + + SLiMAssertScriptSuccess(test_zygosity1); + + std::string test_zygosity2 = // multiple chromosomes of different types + R"V0G0N( + initialize() { + initializeSex(); + initializeMutationType("m1", 0.5, "n", 0.0, 0.01); + initializeGenomicElementType("g1", m1, 1.0); + + ids = 1:6; + symbols = c(1:3, "X", "Y", "MT"); + lengths = rdunif(6, 2e7, 4e7); + types = c(rep("A", 3), "X", "Y", "H"); + + for (id in ids, symbol in symbols, length in lengths, type in types) + { + initializeChromosome(id, length, type, symbol); + initializeMutationRate(1e-7); + initializeRecombinationRate(1e-8); // not used for the Y + initializeGenomicElement(g1); + } + } + 1 early() { + sim.addSubpop("p1", 10); + } + 20 late() { + inds = p1.individuals; + muts = sim.mutations; + + // calculate zygosity + z = inds.zygosityOfMutations(NULL); + + // cross-check row sums: occurrence counts of each mutation + mutCounts1 = rowSums(z); + mutCounts2 = sim.mutationCounts(p1, NULL); + if (!identical(mutCounts1, mutCounts2)) + stop("individual mutation counts do not match"); + + // cross-check column sums: the number of mutations per individual + indCounts1 = colSums(z); + indCounts2 = sapply(inds, "applyValue.haplosomesNonNull.mutations.size();"); + if (!identical(indCounts1, indCounts2)) + stop("individual mutation counts do not match"); + + // cross-check against a zygosity matrix from containsMutations() + m = NULL; + for (ind in inds) + { + zygosity = 0; + + for (chromosome in sim.chromosomes) + { + chr_haplosomes = ind.haplosomesForChromosomes(chromosome, includeNulls=F); + + if (length(chr_haplosomes) == 0) + next; + + chr_muts_indices = which(muts.chromosome == chromosome); + chr_muts = muts[chr_muts_indices]; + + for (hap in chr_haplosomes) + { + chr_muts_counts = hap.containsMutations(chr_muts); + all_muts_counts = rep(0, length(muts)); + all_muts_counts[chr_muts_indices] = chr_muts_counts; + zygosity = zygosity + all_muts_counts; + } + } + + m = cbind(m, zygosity); + } + if (!identical(z, m)) + stop("zygosity matrices do not match"); + } + )V0G0N"; + + SLiMAssertScriptSuccess(test_zygosity2); + + std::string test_zygosity3 = // same but passing mutations=mut explicitly, different code path + R"V0G0N( + initialize() { + initializeSex(); + initializeMutationType("m1", 0.5, "n", 0.0, 0.01); + initializeGenomicElementType("g1", m1, 1.0); + + ids = 1:6; + symbols = c(1:3, "X", "Y", "MT"); + lengths = rdunif(6, 2e7, 4e7); + types = c(rep("A", 3), "X", "Y", "H"); + + for (id in ids, symbol in symbols, length in lengths, type in types) + { + initializeChromosome(id, length, type, symbol); + initializeMutationRate(1e-7); + initializeRecombinationRate(1e-8); // not used for the Y + initializeGenomicElement(g1); + } + } + 1 early() { + sim.addSubpop("p1", 10); + } + 20 late() { + inds = p1.individuals; + muts = sim.mutations; + + // calculate zygosity + z = inds.zygosityOfMutations(NULL); + + // cross-check row sums: occurrence counts of each mutation + mutCounts1 = rowSums(z); + mutCounts2 = sim.mutationCounts(p1, NULL); + if (!identical(mutCounts1, mutCounts2)) + stop("individual mutation counts do not match"); + + // cross-check column sums: the number of mutations per individual + indCounts1 = colSums(z); + indCounts2 = sapply(inds, "applyValue.haplosomesNonNull.mutations.size();"); + if (!identical(indCounts1, indCounts2)) + stop("individual mutation counts do not match"); + + // cross-check against a zygosity matrix from containsMutations() + m = NULL; + for (ind in inds) + { + zygosity = 0; + + for (chromosome in sim.chromosomes) + { + chr_haplosomes = ind.haplosomesForChromosomes(chromosome, includeNulls=F); + + if (length(chr_haplosomes) == 0) + next; + + chr_muts_indices = which(muts.chromosome == chromosome); + chr_muts = muts[chr_muts_indices]; + + for (hap in chr_haplosomes) + { + chr_muts_counts = hap.containsMutations(chr_muts); + all_muts_counts = rep(0, length(muts)); + all_muts_counts[chr_muts_indices] = chr_muts_counts; + zygosity = zygosity + all_muts_counts; + } + } + + m = cbind(m, zygosity); + } + if (!identical(z, m)) + stop("zygosity matrices do not match"); + } + )V0G0N"; + + SLiMAssertScriptSuccess(test_zygosity3); + + std::string test_zygosity4 = // test specified values for hemizygosity and haploidy + R"V0G0N( + initialize() { + initializeSex(); + initializeMutationType("m1", 0.5, "n", 0.0, 0.01); + initializeGenomicElementType("g1", m1, 1.0); + + ids = 1:6; + symbols = c(1:3, "X", "Y", "MT"); + lengths = rdunif(6, 2e7, 4e7); + types = c(rep("A", 3), "X", "Y", "H"); + + for (id in ids, symbol in symbols, length in lengths, type in types) + { + initializeChromosome(id, length, type, symbol); + initializeMutationRate(1e-7); + initializeRecombinationRate(1e-8); // not used for the Y + initializeGenomicElement(g1); + } + } + 1 early() { + sim.addSubpop("p1", 10); + } + 20 late() { + inds = p1.individuals; + muts = sample(sim.mutations, 1000, replace=T); + + // calculate zygosity + z = inds.zygosityOfMutations(muts, hemizygousValue=3, haploidValue=4); + + // check that the correct values were used for each mutation + for (mut in muts, index in seqAlong(muts)) + { + chr = mut.chromosome; + col = z[index,]; + u = sort(unique(col, preserveOrder=F)); + if (chr.type == "A") + expected = c(0, 1, 2); + else if (chr.type == "X") + expected = c(0, 1, 2, 3); + else if (chr.type == "Y") + expected = c(0, 4); + else if (chr.type == "H") + expected = c(0, 4); + + if (any(match(u, expected) == -1)) + stop("for chromosome type " + chr.type + " expected (" + + paste(expected, sep=", ") + ") but saw (" + + paste(u, sep=", ") + ")"); + } + } + )V0G0N"; + + SLiMAssertScriptSuccess(test_zygosity4); + std::cout << "_RunMultitraitTests() done" << std::endl; } From 879f4f58cf55059e86430173c5007c03827114f3 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 3 Jan 2026 17:38:59 -0600 Subject: [PATCH 065/107] fix CI build errors --- core/haplosome.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/haplosome.h b/core/haplosome.h index 02ddb05a..23f48bad 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -60,6 +60,7 @@ class Species; class Population; class Subpopulation; class Individual; +class Individual_Class; class HaplosomeWalker; class MutationBlock; @@ -459,6 +460,7 @@ class Haplosome : public EidosObject friend Subpopulation; friend Chromosome; friend Individual; + friend Individual_Class; friend HaplosomeWalker; }; From d49b98594eaff89768475502bc2ef245019b6f1f Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 4 Jan 2026 12:16:22 -0600 Subject: [PATCH 066/107] a little file reorganization --- core/species.cpp | 255 +++++++++++++++++++++++++---------------------- 1 file changed, 135 insertions(+), 120 deletions(-) diff --git a/core/species.cpp b/core/species.cpp index b132fd06..66cb92cf 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -207,120 +207,21 @@ Species::~Species(void) } } -void Species::_MakeHaplosomeMetadataRecords(void) -{ - // Set up our default metadata records for haplosomes, which are variable-length. The default records - // are used as the initial configuration of the nodes for new individuals; then, as haplosomes are - // added to the new individual, the is_vacant_ bits get tweaked as needed in the recorded metadata, which - // is a bit gross, but necessary; the node metadata is recorded before the haplosomes are created. - // See HaplosomeMetadataRec for comments on this design. - - // First, calculate how many bytes we need - size_t bits_needed_for_is_vacant = chromosomes_.size(); // each chromosome needs one bit per node table entry - haplosome_metadata_size_ = sizeof(HaplosomeMetadataRec) - 1; // -1 to subtract out the is_vacant_[1] in the record - haplosome_metadata_is_vacant_bytes_ = ((bits_needed_for_is_vacant + 7) / 8); // (x+7)/8 rounds up to the number of bytes - haplosome_metadata_size_ += haplosome_metadata_is_vacant_bytes_; - - // Then allocate the buffers needed; the "male" versions are present only when sex is enabled - hap_metadata_1F_ = (HaplosomeMetadataRec *)calloc(haplosome_metadata_size_, 1); - hap_metadata_1M_ = (sex_enabled_ ? (HaplosomeMetadataRec *)calloc(haplosome_metadata_size_, 1) : nullptr); - hap_metadata_2F_ = (HaplosomeMetadataRec *)calloc(haplosome_metadata_size_, 1); - hap_metadata_2M_ = (sex_enabled_ ? (HaplosomeMetadataRec *)calloc(haplosome_metadata_size_, 1) : nullptr); - - // Then set the is_vacant_ bits for the default state for males and females; this is the state in which - // all chromosomes that dictate the is_vacant_ state by sex have that dictated state, while all others - // (types "A", "H", and "H-" only) are assumed to be non-null. Any positions that are unused for a - // given chromosome type (like the second position for type "Y") are given as 1 here, "vacant", by - // definition; "vacant" is either "unused" or "null haplosome". We go from least-significant bit - // to most-significant bit, byte by byte, with each chromosome using two bits. The less significant - // of those two bits is is_vacant_ for haplosome 1 for that chromosome; the more significant of those - // two bits is is_vacant_ for haplosome 2 for that chromosome. - IndividualSex sex = IndividualSex::kFemale; - HaplosomeMetadataRec *focal_metadata_1 = hap_metadata_1F_; - HaplosomeMetadataRec *focal_metadata_2 = hap_metadata_2F_; - - while (true) +Subpopulation *Species::SubpopulationWithName(const std::string &p_subpop_name) { + for (auto subpop_iter : population_.subpops_) { - for (Chromosome *chromosome : chromosomes_) - { - slim_chromosome_index_t chromosome_index = chromosome->Index(); - bool haplosome_1_is_vacant = false, haplosome_2_is_vacant = false; - - switch (chromosome->Type()) - { - case ChromosomeType::kA_DiploidAutosome: - haplosome_1_is_vacant = false; // always present (by default) - haplosome_2_is_vacant = false; // always present (by default) - break; - - case ChromosomeType::kH_HaploidAutosome: - case ChromosomeType::kHF_HaploidFemaleInherited: - case ChromosomeType::kHM_HaploidMaleInherited: - haplosome_1_is_vacant = false; // always present (by default) - haplosome_2_is_vacant = true; // always unused - break; - - case ChromosomeType::kHNull_HaploidAutosomeWithNull: - haplosome_1_is_vacant = false; // always present - haplosome_2_is_vacant = true; // always null - break; - - case ChromosomeType::kX_XSexChromosome: - haplosome_1_is_vacant = false; // always present - haplosome_2_is_vacant = (sex == IndividualSex::kMale); // null in males - break; - - case ChromosomeType::kY_YSexChromosome: - case ChromosomeType::kML_HaploidMaleLine: - haplosome_1_is_vacant = (sex == IndividualSex::kFemale); // null in females - haplosome_2_is_vacant = true; // always unused - break; - - case ChromosomeType::kZ_ZSexChromosome: - haplosome_1_is_vacant = (sex == IndividualSex::kFemale); // null in females - haplosome_2_is_vacant = false; // always present - break; - - case ChromosomeType::kW_WSexChromosome: - case ChromosomeType::kFL_HaploidFemaleLine: - haplosome_1_is_vacant = (sex == IndividualSex::kMale); // null in males - haplosome_2_is_vacant = true; // always unused - break; - - case ChromosomeType::kNullY_YSexChromosomeWithNull: - haplosome_1_is_vacant = true; // always null - haplosome_2_is_vacant = (sex == IndividualSex::kFemale); // null in females - break; - } - - // set the appropriate bits in the focal metadata, which we know was cleared to zero initially - int byte_index = chromosome_index / 8; - int bit_shift = chromosome_index % 8; - - if (haplosome_1_is_vacant) - focal_metadata_1->is_vacant_[byte_index] |= (0x01 << bit_shift); - - if (haplosome_2_is_vacant) - focal_metadata_2->is_vacant_[byte_index] |= (0x01 << bit_shift); - } - - // loop from female to male, then break out - if (sex_enabled_ && (sex == IndividualSex::kFemale)) - { - sex = IndividualSex::kMale; - focal_metadata_1 = hap_metadata_1M_; - focal_metadata_2 = hap_metadata_2M_; - continue; - } - break; + Subpopulation *subpop = subpop_iter.second; + if (subpop->name_ == p_subpop_name) + return subpop; } - -// printf("hap_metadata_1F_ == %.2X\n", hap_metadata_1F_->is_vacant_[0]); -// printf("hap_metadata_1M_ == %.2X\n", hap_metadata_1M_->is_vacant_[0]); -// printf("hap_metadata_2F_ == %.2X\n", hap_metadata_2F_->is_vacant_[0]); -// printf("hap_metadata_2M_ == %.2X\n", hap_metadata_2M_->is_vacant_[0]); + return nullptr; } +// Chromosome management +#pragma mark - +#pragma mark Chromosome management +#pragma mark - + Chromosome *Species::ChromosomeFromID(int64_t p_id) { auto iter = chromosome_from_id_.find(p_id); @@ -534,6 +435,11 @@ void Species::GetChromosomeIndicesFromEidosValue(std::vector &tra } } +// Input/output +#pragma mark - +#pragma mark Input/output +#pragma mark - + // get one line of input, sanitizing by removing comments and whitespace; used only by Species::InitializePopulationFromTextFile void GetInputLine(std::istream &p_input_file, std::string &p_line); void GetInputLine(std::istream &p_input_file, std::string &p_line) @@ -2586,16 +2497,6 @@ void Species::DeleteAllMutationRuns(void) } } -Subpopulation *Species::SubpopulationWithName(const std::string &p_subpop_name) { - for (auto subpop_iter : population_.subpops_) - { - Subpopulation *subpop = subpop_iter.second; - if (subpop->name_ == p_subpop_name) - return subpop; - } - return nullptr; -} - // // Running cycles @@ -7848,6 +7749,120 @@ void Species::RecordAllDerivedStatesFromSLiM(void) } } +void Species::_MakeHaplosomeMetadataRecords(void) +{ + // Set up our default metadata records for haplosomes, which are variable-length. The default records + // are used as the initial configuration of the nodes for new individuals; then, as haplosomes are + // added to the new individual, the is_vacant_ bits get tweaked as needed in the recorded metadata, which + // is a bit gross, but necessary; the node metadata is recorded before the haplosomes are created. + // See HaplosomeMetadataRec for comments on this design. + + // First, calculate how many bytes we need + size_t bits_needed_for_is_vacant = chromosomes_.size(); // each chromosome needs one bit per node table entry + haplosome_metadata_size_ = sizeof(HaplosomeMetadataRec) - 1; // -1 to subtract out the is_vacant_[1] in the record + haplosome_metadata_is_vacant_bytes_ = ((bits_needed_for_is_vacant + 7) / 8); // (x+7)/8 rounds up to the number of bytes + haplosome_metadata_size_ += haplosome_metadata_is_vacant_bytes_; + + // Then allocate the buffers needed; the "male" versions are present only when sex is enabled + hap_metadata_1F_ = (HaplosomeMetadataRec *)calloc(haplosome_metadata_size_, 1); + hap_metadata_1M_ = (sex_enabled_ ? (HaplosomeMetadataRec *)calloc(haplosome_metadata_size_, 1) : nullptr); + hap_metadata_2F_ = (HaplosomeMetadataRec *)calloc(haplosome_metadata_size_, 1); + hap_metadata_2M_ = (sex_enabled_ ? (HaplosomeMetadataRec *)calloc(haplosome_metadata_size_, 1) : nullptr); + + // Then set the is_vacant_ bits for the default state for males and females; this is the state in which + // all chromosomes that dictate the is_vacant_ state by sex have that dictated state, while all others + // (types "A", "H", and "H-" only) are assumed to be non-null. Any positions that are unused for a + // given chromosome type (like the second position for type "Y") are given as 1 here, "vacant", by + // definition; "vacant" is either "unused" or "null haplosome". We go from least-significant bit + // to most-significant bit, byte by byte, with each chromosome using two bits. The less significant + // of those two bits is is_vacant_ for haplosome 1 for that chromosome; the more significant of those + // two bits is is_vacant_ for haplosome 2 for that chromosome. + IndividualSex sex = IndividualSex::kFemale; + HaplosomeMetadataRec *focal_metadata_1 = hap_metadata_1F_; + HaplosomeMetadataRec *focal_metadata_2 = hap_metadata_2F_; + + while (true) + { + for (Chromosome *chromosome : chromosomes_) + { + slim_chromosome_index_t chromosome_index = chromosome->Index(); + bool haplosome_1_is_vacant = false, haplosome_2_is_vacant = false; + + switch (chromosome->Type()) + { + case ChromosomeType::kA_DiploidAutosome: + haplosome_1_is_vacant = false; // always present (by default) + haplosome_2_is_vacant = false; // always present (by default) + break; + + case ChromosomeType::kH_HaploidAutosome: + case ChromosomeType::kHF_HaploidFemaleInherited: + case ChromosomeType::kHM_HaploidMaleInherited: + haplosome_1_is_vacant = false; // always present (by default) + haplosome_2_is_vacant = true; // always unused + break; + + case ChromosomeType::kHNull_HaploidAutosomeWithNull: + haplosome_1_is_vacant = false; // always present + haplosome_2_is_vacant = true; // always null + break; + + case ChromosomeType::kX_XSexChromosome: + haplosome_1_is_vacant = false; // always present + haplosome_2_is_vacant = (sex == IndividualSex::kMale); // null in males + break; + + case ChromosomeType::kY_YSexChromosome: + case ChromosomeType::kML_HaploidMaleLine: + haplosome_1_is_vacant = (sex == IndividualSex::kFemale); // null in females + haplosome_2_is_vacant = true; // always unused + break; + + case ChromosomeType::kZ_ZSexChromosome: + haplosome_1_is_vacant = (sex == IndividualSex::kFemale); // null in females + haplosome_2_is_vacant = false; // always present + break; + + case ChromosomeType::kW_WSexChromosome: + case ChromosomeType::kFL_HaploidFemaleLine: + haplosome_1_is_vacant = (sex == IndividualSex::kMale); // null in males + haplosome_2_is_vacant = true; // always unused + break; + + case ChromosomeType::kNullY_YSexChromosomeWithNull: + haplosome_1_is_vacant = true; // always null + haplosome_2_is_vacant = (sex == IndividualSex::kFemale); // null in females + break; + } + + // set the appropriate bits in the focal metadata, which we know was cleared to zero initially + int byte_index = chromosome_index / 8; + int bit_shift = chromosome_index % 8; + + if (haplosome_1_is_vacant) + focal_metadata_1->is_vacant_[byte_index] |= (0x01 << bit_shift); + + if (haplosome_2_is_vacant) + focal_metadata_2->is_vacant_[byte_index] |= (0x01 << bit_shift); + } + + // loop from female to male, then break out + if (sex_enabled_ && (sex == IndividualSex::kFemale)) + { + sex = IndividualSex::kMale; + focal_metadata_1 = hap_metadata_1M_; + focal_metadata_2 = hap_metadata_2M_; + continue; + } + break; + } + +// printf("hap_metadata_1F_ == %.2X\n", hap_metadata_1F_->is_vacant_[0]); +// printf("hap_metadata_1M_ == %.2X\n", hap_metadata_1M_->is_vacant_[0]); +// printf("hap_metadata_2F_ == %.2X\n", hap_metadata_2F_->is_vacant_[0]); +// printf("hap_metadata_2M_ == %.2X\n", hap_metadata_2M_->is_vacant_[0]); +} + void Species::MetadataForMutation(Mutation *p_mutation, MutationMetadataRec *p_metadata) { static_assert(sizeof(MutationMetadataRec) == 17, "MutationMetadataRec has changed size; this code probably needs to be updated"); From e572849f0aeb9dd014d92d3f0259dac0fc2d4eed Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 5 Jan 2026 16:02:41 -0600 Subject: [PATCH 067/107] some optimization flag and timing restriction cleanup --- core/chromosome.cpp | 10 ++ core/community.h | 6 ++ core/community_eidos.cpp | 48 ++++++++++ core/genomic_element_type.cpp | 24 +++-- core/haplosome.cpp | 42 ++++---- core/individual.cpp | 26 +++-- core/mutation.cpp | 78 +++++++++------ core/mutation_type.cpp | 18 ++-- core/mutation_type.h | 8 +- core/population.cpp | 15 ++- core/slim_eidos_block.cpp | 12 ++- core/slim_test_genetics.cpp | 6 +- core/species.cpp | 47 +++++---- core/species.h | 9 +- core/species_eidos.cpp | 174 +++++++++++++++++----------------- core/subpopulation.cpp | 51 +++------- 16 files changed, 351 insertions(+), 223 deletions(-) diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 6241a77e..1b460f8e 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1045,6 +1045,11 @@ MutationIndex Chromosome::DrawNewMutation(std::pairis_neutral_) + species_.NoteNonNeutralMutation(mutation); + return new_mut_index; } @@ -1442,6 +1447,11 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pairis_neutral_) + species_.NoteNonNeutralMutation(mutation); + return new_mut_index; } diff --git a/core/community.h b/core/community.h index 55b8311d..f5179fcf 100644 --- a/core/community.h +++ b/core/community.h @@ -361,6 +361,12 @@ class Community : public EidosDictionaryUnretained static const std::vector *SLiMFunctionSignatures(void); // all non-zero-tick functions static void AddSLiMFunctionsToMap(EidosFunctionMap &p_map); + // timing restriction enforcement + // FIXME MULTITRAIT should add more timing restriction categories like this, to share the code and make things more readable + void EnforceTimingRestriction_EventBlockOnly(const char *p_method_name, const char *p_eidos_name, const char *p_addendum); + void EnforceTimingRestriction_ReproductionCallbackOnly(const char *p_method_name, const char *p_eidos_name, const char *p_addendum); + void EnforceTimingRestriction_FirstEventStageOnly(const char *p_method_name, const char *p_eidos_name, const char *p_addendum); + EidosValue_SP ExecuteContextFunction_initializeSLiMModelType(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteContextFunction_initializeInteractionType(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter); diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index c771048b..3c82a32d 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -190,6 +190,32 @@ EidosSymbolTable *Community::SymbolsFromBaseSymbols(EidosSymbolTable *p_base_sym return simulation_constants_; } +void Community::EnforceTimingRestriction_EventBlockOnly(const char *p_method_name, const char *p_eidos_name, const char *p_addendum) +{ + // TIMING RESTRICTION + // must be called directly from an event block -- not from a callback, even if the callback was triggered inside the event block + if ((executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) + EIDOS_TERMINATION << "ERROR (" << p_method_name << "): " << p_eidos_name << " must be called directly from a first(), early(), or late() event" << p_addendum << "." << EidosTerminate(); +} + +void Community::EnforceTimingRestriction_ReproductionCallbackOnly(const char *p_method_name, const char *p_eidos_name, const char *p_addendum) +{ + // TIMING RESTRICTION + // must be called directly from a reproduction() callback -- not from another callback, even if the callback was triggered inside the event block + if (executing_block_type_ != SLiMEidosBlockType::SLiMEidosReproductionCallback) + EIDOS_TERMINATION << "ERROR (" << p_method_name << "): " << p_eidos_name << " must be called directly from a reproduction() callback" << p_addendum << "." << EidosTerminate(); +} + +void Community::EnforceTimingRestriction_FirstEventStageOnly(const char *p_method_name, const char *p_eidos_name, const char *p_addendum) +{ + SLiMCycleStage cycle_stage = CycleStage(); + + // TIMING RESTRICTION + // must be called during the first() event tick cycle stage, but can be within a called block during that stage + if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts)) + EIDOS_TERMINATION << "ERROR (" << p_method_name << "): " << p_eidos_name << " must be called from a first() event" << p_addendum << "." << EidosTerminate(); +} + // ********************* (void)initializeSLiMModelType(string$ modelType) // EidosValue_SP Community::ExecuteContextFunction_initializeSLiMModelType(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -720,6 +746,17 @@ EidosValue_SP Community::ExecuteMethod_deregisterScriptBlock(EidosGlobalStringID } else { + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (block->species_spec_ && ((block->type_ == SLiMEidosBlockType::SLiMEidosFitnessEffectCallback) || (block->type_ == SLiMEidosBlockType::SLiMEidosMutationEffectCallback))) + { + if (block->species_spec_->InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_deregisterScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be deregistered within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + if (block->species_spec_->Active() && ((cycle_stage_ == SLiMCycleStage::kWFStage6CalculateFitness) || (cycle_stage_ == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_deregisterScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be deregistered during the fitness recalculation tick cycle stage." << EidosTerminate(); + } + // all other script blocks go on the main list and get cleared out at the end of each cycle stage if (std::find(scheduled_deregistrations_.begin(), scheduled_deregistrations_.end(), block) != scheduled_deregistrations_.end()) EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_deregisterScriptBlock): deregisterScriptBlock() called twice on the same script block." << EidosTerminate(); @@ -1164,6 +1201,17 @@ EidosValue_SP Community::ExecuteMethod_rescheduleScriptBlock(EidosGlobalStringID EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_rescheduleScriptBlock): (internal error) rescheduleScriptBlock() cannot be called on user-defined function script blocks." << EidosTerminate(); } + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (block->species_spec_ && ((block->type_ == SLiMEidosBlockType::SLiMEidosFitnessEffectCallback) || (block->type_ == SLiMEidosBlockType::SLiMEidosMutationEffectCallback))) + { + if (block->species_spec_->InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_rescheduleScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be rescheduled within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + if (block->species_spec_->Active() && ((cycle_stage_ == SLiMCycleStage::kWFStage6CalculateFitness) || (cycle_stage_ == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_rescheduleScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be rescheduled during the fitness recalculation tick cycle stage." << EidosTerminate(); + } + SLiMCycleStage stage = CycleStageForScriptBlockType(block->type_); if ((!start_null || !end_null) && ticks_null) diff --git a/core/genomic_element_type.cpp b/core/genomic_element_type.cpp index c5a4a124..e2868462 100644 --- a/core/genomic_element_type.cpp +++ b/core/genomic_element_type.cpp @@ -397,18 +397,28 @@ EidosValue_SP GenomicElementType::ExecuteMethod_setMutationFractions(EidosGlobal MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutationTypes_value, mut_type_index, &species_.community_, &species_, "setMutationFractions()"); // SPECIES CONSISTENCY CHECK double proportion = proportions_value->NumericAtIndex_NOCAST(mut_type_index, nullptr); - if ((proportion < 0) || !std::isfinite(proportion)) // == 0 is allowed but must be fixed before the simulation executes; see InitializeDraws() + if ((proportion < 0) || !std::isfinite(proportion)) // == 0 is allowed but the muttype will not be added; see also InitializeDraws() EIDOS_TERMINATION << "ERROR (GenomicElementType::ExecuteMethod_setMutationFractions): setMutationFractions() proportions must be greater than or equal to zero (" << EidosStringForFloat(proportion) << " supplied)." << EidosTerminate(); if (std::find(mutation_types.begin(), mutation_types.end(), mutation_type_ptr) != mutation_types.end()) EIDOS_TERMINATION << "ERROR (GenomicElementType::ExecuteMethod_setMutationFractions): setMutationFractions() mutation type m" << mutation_type_ptr->mutation_type_id_ << " used more than once." << EidosTerminate(); - mutation_types.emplace_back(mutation_type_ptr); - mutation_fractions.emplace_back(proportion); - - // check whether we are now using a mutation type that is non-neutral; check and set pure_neutral_ - if (!mutation_type_ptr->all_neutral_DES_) - species_.pure_neutral_ = false; + // BCH 1/4/2026: new policy: if the muttype's proportion is zero, we don't really consider it to be used. + // This allows our decisions about whether the simulation is neutral or not to be more accurate. + if (proportion > 0.0) + { + mutation_types.emplace_back(mutation_type_ptr); + mutation_fractions.emplace_back(proportion); + + // let the mutation type know that it is being used + mutation_type_ptr->used_in_GEType_ = true; + + // check whether the mutation type is non-neutral; if so, the species is now considered non-neutral + // (because we expect that a non-neutral mutation will be generated by this genomic element type) + // see also Species::ExecuteContextFunction_initializeGenomicElementType() for the same logic + if (!mutation_type_ptr->all_neutral_DES_) + species_.species_all_neutral_mutations_ = false; + } } // Everything seems to be in order, so replace our mutation info with the new info diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 4db355e8..6a4ea627 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2582,7 +2582,10 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addMutations(EidosGlobalStringID p_ target_run->insert_sorted_mutation_if_unique(mut_block_ptr, mutation_block->IndexInBlock(mut_to_add)); // No need to add the mutation to the registry; how would the user ever get a Mutation that was not already in it? - // Similarly, no need to check and set pure_neutral_ and all_neutral_mutations_; the mutation is already in the system + + // BCH 1/4/2025: the mutation is already in the system, so for now we don't need to note it + //if (!mut_to_add->is_neutral_) + // species->NoteNonNeutralMutation(mut_to_add); } } } @@ -2962,9 +2965,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID { new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, origin_subpop_id, origin_tick, (int8_t)nucleotide); - // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - if (!mutation_type_ptr->all_neutral_DES_) - species->pure_neutral_ = false; + // this mutation will be added to the simulation somewhere, so tell the species about it + if (!new_mut->is_neutral_) + species->NoteNonNeutralMutation(new_mut); } else // (p_method_id == gID_addNewMutation) { @@ -2982,13 +2985,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // FIXME MULTITRAIT this code will also now need to handle the independent dominance case new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); - // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - // The selection coefficient was supplied by the user (i.e., not be from the mutation type's DES), so we set all_neutral_mutations_ also - if (selection_coeff != (slim_effect_t)0.0) - { - species->pure_neutral_ = false; - mutation_type_ptr->all_neutral_mutations_ = false; - } + // this mutation will be added to the simulation somewhere, so tell the species about it + if (!new_mut->is_neutral_) + species->NoteNonNeutralMutation(new_mut); } // add to the registry, return value, haplosome, etc. @@ -3485,14 +3484,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr // FIXME MULTITRAIT this code will also now need to handle the independent dominance case Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); - // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - if (selection_coeff != (slim_effect_t)0.0) - { - species.pure_neutral_ = false; - - // the selection coefficient was drawn from the mutation type's DES, so there is no need to set all_neutral_mutations_ - //mutation_type_ptr->all_neutral_mutations_ = false; - } + // this mutation will be added to the simulation somewhere, so tell the species about it + if (!new_mut->is_neutral_) + species.NoteNonNeutralMutation(new_mut); // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry pop.MutationRegistryAdd(new_mut); @@ -4142,13 +4136,9 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } - // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DES), so we set all_neutral_mutations_ also - if (selection_coeff != (slim_effect_t)0.0) - { - species->pure_neutral_ = false; - mutation_type_ptr->all_neutral_mutations_ = false; - } + // this mutation will be added to the simulation somewhere, so tell the species about it + if (!new_mut->is_neutral_) + species->NoteNonNeutralMutation(new_mut); // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry pop.MutationRegistryAdd(new_mut); diff --git a/core/individual.cpp b/core/individual.cpp index 642c3318..aaead750 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -5504,12 +5504,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } - // This mutation type might not be used by any genomic element type (i.e. might not already be vetted), so we need to check and set pure_neutral_ - if (selection_coeff != (slim_effect_t)0.0) - { - species->pure_neutral_ = false; - mutation_type_ptr->all_neutral_mutations_ = false; - } + // all mutations seen here will be added to the simulation somewhere, so tell the species about it + if (!new_mut->is_neutral_) + species->NoteNonNeutralMutation(new_mut); // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry pop.MutationRegistryAdd(new_mut); @@ -6387,6 +6384,20 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotype(EidosGlobalStringI if (!species) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_demandPhenotype): demandPhenotype() requires that all individuals belong to the same species." << EidosTerminate(); + Community &community = species->community_; + + // TIMING RESTRICTION + // demandPhenotype() is strictly limited to first()/early()/late() events; it cannot be called + // from other contexts even for a different species than executing_species_. This is because + // it can have the side effect of running mutationEffect() callbacks, and those cannot nest inside + // the execution of a different species. + community.EnforceTimingRestriction_EventBlockOnly("Individual_Class::ExecuteMethod_demandPhenotype", "demandPhenotype()", ""); + if (species->InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_demandPhenotype): demandPhenotype() cannot be called when trait/fitness calculation is already underway." << EidosTerminate(); + + // mark that we are doing trait calculations, to block changes to callbacks in use + species->SetInsideTraitOrFitnessCalculation(true); + // get the trait indices, with bounds-checking std::vector trait_indices; species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "demandPhenotype"); @@ -6403,6 +6414,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotype(EidosGlobalStringI else DemandPhenotype(species, individuals_buffer, individuals_count, trait_indices); + // done with trait calculations, unblock + species->SetInsideTraitOrFitnessCalculation(false); + // BCH 12/25/2025: I considered having this return the trait values that were demanded; but I think void is // better. Collecting the trait values would be additional work here that would not always be desired, so // it's better to make the user do it separately if they want it. Also, this method should generally be diff --git a/core/mutation.cpp b/core/mutation.cpp index 054c49e3..294107e1 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -90,10 +90,6 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ { is_neutral_ = false; - species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation - mutation_type_ptr_->all_neutral_mutations_ = false; // let the mutation type for this mutation know that it is no longer neutral - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! - // get the realized dominance to handle the possibility of independent dominance slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -127,6 +123,16 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ } } + // this mutation will be added to the simulation somewhere, so tell the species about it + // (OK, it might not get added due to stacking policy or mutation() callbacks, but we assume it will be) + if (!is_neutral_) + { + species.NoteNonNeutralMutation(this); + + // FIXME MULTITRAIT maybe this should get managed by NoteNonNeutralMutation() as well? seems like part of its duties... + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! + } + #if DEBUG SelfConsistencyCheck(" in Mutation::Mutation()"); #endif @@ -200,7 +206,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ } else { - // The DES of the mutation type is not pure neutral. Note that species.pure_neutral_ might still be true + // The DES of the mutation type is not pure neutral. Note that species.species_all_neutral_mutations_ might still be true // at this point; the mutation type for this mutation might not be used by any genomic element type, // because we might be getting called by addNewDrawnMutation() for a type that is otherwise unused. for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) @@ -221,9 +227,6 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ { is_neutral_ = false; - species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! - // get the realized dominance to handle the possibility of independent dominance slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -258,6 +261,16 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ } } + // this mutation will be added to the simulation somewhere, so tell the species about it + // (OK, it might not get added due to stacking policy or mutation() callbacks, but we assume it will be) + if (!is_neutral_) + { + species.NoteNonNeutralMutation(this); + + // FIXME MULTITRAIT maybe this should get managed by NoteNonNeutralMutation() as well? seems like part of its duties... + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! + } + #if DEBUG SelfConsistencyCheck(" in Mutation::Mutation()"); #endif @@ -317,10 +330,6 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ { is_neutral_ = false; - species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation - mutation_type_ptr_->all_neutral_mutations_ = false; // let the mutation type for this mutation know that it is no longer pure neutral - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! - // get the realized dominance to handle the possibility of independent dominance slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -354,6 +363,16 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ } } + // this mutation will be added to the simulation somewhere, so tell the species about it + // (OK, it might not get added due to stacking policy or mutation() callbacks, but we assume it will be) + if (!is_neutral_) + { + species.NoteNonNeutralMutation(this); + + // FIXME MULTITRAIT maybe this should get managed by NoteNonNeutralMutation() as well? seems like part of its duties... + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! + } + #if DEBUG SelfConsistencyCheck(" in Mutation::Mutation()"); #endif @@ -486,18 +505,6 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e if (p_new_effect != (slim_effect_t)0.0) { - if (old_effect == (slim_effect_t)0.0) - { - // This mutation is no longer neutral; various observers care about that change - is_neutral_ = false; - - Species &species = mutation_type_ptr_->species_; - - species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation - mutation_type_ptr_->all_neutral_mutations_ = false; // let the mutation type for this mutation know that it is no longer pure neutral - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags - } - slim_effect_t realized_dominance = RealizedDominanceForTrait(p_trait); slim_effect_t hemizygous_dominance = traitInfoRec->hemizygous_dominance_coeff_; @@ -520,6 +527,15 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * p_new_effect); // 2ha traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * p_new_effect); // 2ha (using h_hemi) } + + // This mutation is no longer neutral; various observers care about that change + Species &species = mutation_type_ptr_->species_; + + is_neutral_ = false; + species.NoteNonNeutralMutation(this); + + // FIXME MULTITRAIT maybe this should get managed by NoteNonNeutralMutation() as well? seems like part of its duties... + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags } else // p_new_effect == 0.0; therefore, old_effect != 0.0 { @@ -541,7 +557,7 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e } } - // Note that we cannot set species.pure_neutral_ and mutation_type_ptr_->all_neutral_mutations_ to + // Note that we cannot set species.species_all_neutral_mutations_ and mutation_type_ptr_->all_neutral_mutations_ to // false here, because only this mutation has changed to neutral; other mutations might be non-neutral species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags @@ -590,6 +606,10 @@ void Mutation::SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, sli { traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); } + + // when non-neutral mutation characteristics change, the species needs to be notified + if (!is_neutral_) + mutation_type_ptr_->species_.NoteNonNeutralMutation(this); } void Mutation::SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) @@ -608,6 +628,10 @@ void Mutation::SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitIn { traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); } + + // when non-neutral mutation characteristics change, the species needs to be notified + if (!is_neutral_) + mutation_type_ptr_->species_.NoteNonNeutralMutation(this); } void Mutation::SelfDelete(void) @@ -1363,9 +1387,9 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth // We take just the mutation type pointer; if the user wants a new selection coefficient, they can do that themselves mutation_type_ptr_ = mutation_type_ptr; - // If we are non-neutral, make sure the mutation type knows it is now also non-neutral + // when non-neutral mutation characteristics change, the species needs to be notified if (!is_neutral_) - mutation_type_ptr_->all_neutral_mutations_ = false; + mutation_type_ptr_->species_.NoteNonNeutralMutation(this); // Changing the mutation type no longer changes the dominance coefficient or the hemizygous dominance // coefficient, so there are no longer any side effects on trait effects / fitness to be managed here. diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 5d2e584a..16813926 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -83,12 +83,13 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr // intentionally no bounds check for dominance_coeff_ // determine whether this mutation type has a neutral DES - // note that we do not set Species.pure_neutral_ here; we wait until this muttype is used + // note that we do not set species_all_neutral_mutations_ = false here; we wait until this muttype is used + // see Species::ExecuteContextFunction_initializeGenomicElementType() and similar spots for that logic all_neutral_DES_ = ((p_DES_type == DESType::kFixed) && (p_DES_parameters[0] == 0.0)); // initially, whether a mutation type has any neutral mutations is inherited from whether it has a neutral DES // note that this flag will be cleared if any mutation of this type has its effect changed to non-neutral - all_neutral_mutations_ = all_neutral_DES_; + muttype_all_neutral_mutations_ = all_neutral_DES_; // set up DE entries for all traits; every trait is initialized identically, from the parameters given EffectDistributionInfo DES_info; @@ -1046,12 +1047,17 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo all_neutral_DES_ = false; } - // if our DES is non-neutral, set pure_neutral_ and all_neutral_mutations_ to false; - // these flags are sticky, so we don't try to set them back to true again if (!all_neutral_DES_) { - species_.pure_neutral_ = false; - all_neutral_mutations_ = false; + // if our DES is non-neutral and we're used in a GenomicElementType, that implies that the simulation is + // now non-neutral (since we expect that a non-neutral mutation will now be generated by the GEType) + // see also Species::ExecuteContextFunction_initializeGenomicElementType() for the same logic + if (used_in_GEType_) + species_.species_all_neutral_mutations_ = false; + + // if our DES is non-neutral, we set muttype_all_neutral_mutations_ to false to track that we are non-neutral + // see also MutationType::MutationType() for the same logic at init time + muttype_all_neutral_mutations_ = false; } return gStaticEidosValueVOID; diff --git a/core/mutation_type.h b/core/mutation_type.h index 771be138..0cf8b85d 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -129,12 +129,18 @@ class MutationType : public EidosDictionaryUnretained MutationRun muttype_registry_; #endif + // OPTIMIZATION FLAGS + // For optimizing phenotype calculations, the exact situation for each mutation type is of great interest: // does it have a neutral DES, and if so has any mutation of that type had its selection coefficient changed // to be non-zero, are mutations of this type made neutral by a constant callback like "return 1.0;", and so // forth. Different parts of the code need to know slightly different things, so we have several different // flags of this sort. The subtle differences between these flags can be crucially important! + // this flag is set if the mutation type is ever used in a GenomicElementType; if a mutation type + // is never used in a GenomicElementType, it does not affect whether the simulation is non-neutral + mutable bool used_in_GEType_; + // all_neutral_DES_ is true if and only if the DES for all traits is "f" 0.0. Mutations of this type // could still be non-neutral (because they were changed, or created at a time when the DES was not neutral), // and callbacks could still change mutation effects. What this flag does tell you is that if a new mutation @@ -145,7 +151,7 @@ class MutationType : public EidosDictionaryUnretained // all_pure_neutral_mutations_ is true if any mutation of this type could be non-neutral. That is the case // if (a) the mutation type has ever had a non-neutral DES, or (b) if any mutation of this type has ever been // configured to be non-neutral. This flag is "sticky"; once set to true it will remain true forever. - mutable bool all_neutral_mutations_; + mutable bool muttype_all_neutral_mutations_; // is_pure_neutral_now_ is set up by Subpopulation::UpdateFitness(), and is valid only inside a given UpdateFitness() call. // If set, it indicates that the mutation type is currently pure neutral – either because all_neutral_DES_ is set and the diff --git a/core/population.cpp b/core/population.cpp index f4432c8e..c906a8c3 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -3232,6 +3232,8 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h if (new_mutation != -1) mutations_to_add.emplace_back(new_mutation); // positions are already sorted + // NoteNonNeutralMutation() has already been called by DrawNewMutationExtended() if necessary + // see further comments below, in the non-nucleotide case; they apply here as well } } @@ -3244,7 +3246,8 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // no need to worry about pure_neutral_ or all_neutral_mutations_ here; the mutation is drawn from a registered genomic element type + // NoteNonNeutralMutation() has already been called by DrawNewMutation() if necessary + // we can't handle the stacking policy here, since we don't yet know what the context of the new mutation will be; we do it below // we add the new mutation to the registry below, if the stacking policy says the mutation can actually be added } @@ -3874,6 +3877,8 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha if (new_mutation != -1) mutations_to_add.emplace_back(new_mutation); // positions are already sorted + // NoteNonNeutralMutation() has already been called by DrawNewMutationExtended() if necessary + // see further comments below, in the non-nucleotide case; they apply here as well } } @@ -3886,7 +3891,8 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // no need to worry about pure_neutral_ or all_neutral_mutations_ here; the mutation is drawn from a registered genomic element type + // NoteNonNeutralMutation() has already been called by DrawNewMutation() if necessary + // we can't handle the stacking policy here, since we don't yet know what the context of the new mutation will be; we do it below // we add the new mutation to the registry below, if the stacking policy says the mutation can actually be added } @@ -4276,6 +4282,8 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil if (new_mutation != -1) mutations_to_add.emplace_back(new_mutation); // positions are already sorted + // DrawNewMutationExtended() has already been called by DrawNewMutation() if necessary + // see further comments below, in the non-nucleotide case; they apply here as well } } @@ -4288,7 +4296,8 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // no need to worry about pure_neutral_ or all_neutral_mutations_ here; the mutation is drawn from a registered genomic element type + // NoteNonNeutralMutation() has already been called by DrawNewMutation() if necessary + // we can't handle the stacking policy here, since we don't yet know what the context of the new mutation will be; we do it below // we add the new mutation to the registry below, if the stacking policy says the mutation can actually be added } diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index 7f5a9405..6fdc2e4e 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -1754,8 +1754,18 @@ void SLiMEidosBlock::SetProperty(EidosGlobalStringID p_property_id, const EidosV if (value && ((species_spec_ && !species_spec_->Active()) || (ticks_spec_ && !ticks_spec_->Active()))) EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SetProperty): property active cannot be used to activate a block that is inactive because of a 'species' or 'ticks' specifier in its declaration, or because it was deactivated by a call to skipTick()." << EidosTerminate(); - block_active_ = value; + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (species_spec_ && ((type_ == SLiMEidosBlockType::SLiMEidosFitnessEffectCallback) || (type_ == SLiMEidosBlockType::SLiMEidosMutationEffectCallback))) + { + if (species_spec_->InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SetProperty): the active property of fitnessEffect() and mutationEffect() callback script blocks may not be set within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + if (species_spec_->Active() && ((species_spec_->community_.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (species_spec_->community_.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SetProperty): the active property of fitnessEffect() and mutationEffect() callback script blocks may not be set during the fitness recalculation tick cycle stage." << EidosTerminate(); + } + block_active_ = value; return; } diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 7bf8527b..d8f3b361 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -173,8 +173,10 @@ void _RunGenomicElementTypeTests(void) // Test GenomicElementType - (void)setMutationFractions(io mutationTypes, numeric proportions) SLiMAssertScriptStop(gen1_setup + "1 early() { g1.setMutationFractions(object(), integer(0)); stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { g1.setMutationFractions(m1, 0.0); if (g1.mutationTypes == m1 & g1.mutationFractions == 0.0) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { g1.setMutationFractions(1, 0.0); if (g1.mutationTypes == m1 & g1.mutationFractions == 0.0) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { g1.setMutationFractions(m1, 0.0); if (g1.mutationTypes.size() == 0 & size(g1.mutationFractions) == 0) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { g1.setMutationFractions(1, 0.0); if (g1.mutationTypes.size() == 0 & size(g1.mutationFractions) == 0) stop(); }", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { sim.addSubpop('p1', 10); } 1 early() { g1.setMutationFractions(m1, 0.0); } 100 early() {}", "empty mutation type vector", __LINE__, false); + SLiMAssertScriptRaise(gen1_setup + "1 early() { sim.addSubpop('p1', 10); } 1 early() { g1.setMutationFractions(1, 0.0); } 100 early() {}", "empty mutation type vector", __LINE__, false); SLiMAssertScriptStop(gen1_setup + "1 early() { g1.setMutationFractions(m1, 0.3); if (g1.mutationTypes == m1 & g1.mutationFractions == 0.3) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { g1.setMutationFractions(1, 0.3); if (g1.mutationTypes == m1 & g1.mutationFractions == 0.3) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "initialize() { initializeMutationType('m2', 0.7, 'e', 0.5); } 1 early() { g1.setMutationFractions(c(m1,m2), c(0.3, 0.7)); if (identical(g1.mutationTypes, c(m1,m2)) & identical(g1.mutationFractions, c(0.3,0.7))) stop(); }", __LINE__); diff --git a/core/species.cpp b/core/species.cpp index 66cb92cf..895c5c17 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -217,6 +217,20 @@ Subpopulation *Species::SubpopulationWithName(const std::string &p_subpop_name) return nullptr; } +void Species::NoteNonNeutralMutation(Mutation *p_mut) +{ + // This should be called whenever a non-neutral mutation is added to the simulation, or an existing + // mutation is made non-neutral, or an existing non-neutral mutation changes mutation type: anything + // that would affect the optimization flags we keep. The goal here is to centralize this logic. +#if DEBUG + if (p_mut->is_neutral_) + EIDOS_TERMINATION << "ERROR (Species::NoteNonNeutralMutation): (internal error) NoteNonNeutralMutation() called for neutral mutation." << EidosTerminate(); +#endif + + species_all_neutral_mutations_ = false; + p_mut->mutation_type_ptr_->muttype_all_neutral_mutations_ = false; +} + // Chromosome management #pragma mark - #pragma mark Chromosome management @@ -1329,12 +1343,9 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromTextFile): (internal error) separate muttype registries set up during pop load." << EidosTerminate(); #endif - // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_neutral_mutations_ - if (selection_coeff != (slim_effect_t)0.0) - { - pure_neutral_ = false; - mutation_type_ptr->all_neutral_mutations_ = false; - } + // all mutations seen here will be added to the simulation somewhere, so tell the species about it + if (!new_mut->is_neutral_) + NoteNonNeutralMutation(new_mut); } population_.InvalidateMutationReferencesCache(); @@ -2096,12 +2107,9 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): (internal error) separate muttype registries set up during pop load." << EidosTerminate(); #endif - // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_neutral_mutations_ - if (selection_coeff != (slim_effect_t)0.0) - { - pure_neutral_ = false; - mutation_type_ptr->all_neutral_mutations_ = false; - } + // all mutations seen here will be added to the simulation somewhere, so tell the species about it + if (!new_mut->is_neutral_) + NoteNonNeutralMutation(new_mut); } population_.InvalidateMutationReferencesCache(); @@ -9950,6 +9958,10 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapis_neutral_) + // NoteNonNeutralMutation(new_mut); } else { @@ -9968,13 +9980,10 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapall_neutral_mutations_ = false; + + // all mutations seen here will be added to the simulation somewhere, so tell the species about it + if (!new_mut->is_neutral_) + NoteNonNeutralMutation(new_mut); } } } diff --git a/core/species.h b/core/species.h index 9e78a857..befe4002 100644 --- a/core/species.h +++ b/core/species.h @@ -179,6 +179,8 @@ class Species : public EidosDictionaryUnretained slim_tick_t tick_modulo_ = 1; // the species is active every tick_modulo_ ticks slim_tick_t tick_phase_ = 1; // the species is first active in tick tick_phase_ + bool inside_trait_or_fitness_calculation_ = false; // a flag to prevent re-entry and prohibited operations during trait/fitness calculations + std::string color_; // color to use when displayed (in SLiMgui) float color_red_, color_green_, color_blue_; // cached color components from color_; should always be in sync @@ -393,7 +395,7 @@ class Species : public EidosDictionaryUnretained // changed to non-neutral. The flag is never set back to true. Importantly, simply defining a non-neutral mutation type does NOT clear this flag; we want sims to be // able to run a neutral burn-in at full speed, only slowing down when the non-neutral mutation type is actually used. BCH 12 January 2018: Also, note that this flag // is unaffected by the fitness_scaling_ properties on Subpopulation and Individual, which are taken into account even when this flag is set. - bool pure_neutral_ = true; // optimization flag + bool species_all_neutral_mutations_ = true; // optimization flag // this flag tracks whether a type 's' mutation type has ever been seen; we just set it to true if we see one, we never set it back to false again, for simplicity // this switches to a less optimized case when evolving in WF models, if a type 's' DES could be present, since that can open up various cans of worms @@ -428,6 +430,8 @@ class Species : public EidosDictionaryUnretained Species(Community &p_community, slim_objectid_t p_species_id, const std::string &p_name); // construct a Species from a community / id / name ~Species(void); // destructor + void NoteNonNeutralMutation(Mutation *p_mut); // call whenever a non-neutral mutation needs to be noted to update optimization flags + // Chromosome configuration and access inline __attribute__((always_inline)) const std::vector &Chromosomes(void) { return chromosomes_; } inline __attribute__((always_inline)) const std::vector &ChromosomesForHaplosomeIndices(void) { return chromosome_for_haplosome_index_; } @@ -535,6 +539,9 @@ class Species : public EidosDictionaryUnretained inline __attribute__((always_inline)) const std::map &GenomicElementTypes(void) { return genomic_element_types_; } inline __attribute__((always_inline)) size_t GraveyardSize(void) const { return graveyard_.size(); } + inline __attribute__((always_inline)) bool InsideTraitOrFitnessCalculation(void) const { return inside_trait_or_fitness_calculation_; } + inline __attribute__((always_inline)) void SetInsideTraitOrFitnessCalculation(bool p_flag) { inside_trait_or_fitness_calculation_ = p_flag; } + inline Subpopulation *SubpopulationWithID(slim_objectid_t p_subpop_id) { auto id_iter = population_.subpops_.find(p_subpop_id); return (id_iter == population_.subpops_.end()) ? nullptr : id_iter->second; diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 48d9db1c..99f56bfc 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -502,35 +502,47 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const if (community_.GenomicElementTypeWithID(map_identifier)) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeGenomicElementType): initializeGenomicElementType() genomic element type g" << map_identifier << " already defined." << EidosTerminate(); - int mut_type_id_count = mutationTypes_value->Count(); - int proportion_count = proportions_value->Count(); - - if (mut_type_id_count != proportion_count) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeGenomicElementType): initializeGenomicElementType() requires the sizes of mutationTypes and proportions to be equal." << EidosTerminate(); - std::vector mutation_types; std::vector mutation_fractions; - for (int mut_type_index = 0; mut_type_index < mut_type_id_count; ++mut_type_index) { - MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutationTypes_value, mut_type_index, &community_, this, "initializeGenomicElementType()"); // SPECIES CONSISTENCY CHECK - double proportion = proportions_value->NumericAtIndex_NOCAST(mut_type_index, nullptr); - - if ((proportion < 0) || !std::isfinite(proportion)) // == 0 is allowed but must be fixed before the simulation executes; see InitializeDraws() - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeGenomicElementType): initializeGenomicElementType() proportions must be greater than or equal to zero (" << EidosStringForFloat(proportion) << " supplied)." << EidosTerminate(); - - if (std::find(mutation_types.begin(), mutation_types.end(), mutation_type_ptr) != mutation_types.end()) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeGenomicElementType): initializeGenomicElementType() mutation type m" << mutation_type_ptr->mutation_type_id_ << " used more than once." << EidosTerminate(); - - if (nucleotide_based_ && !mutation_type_ptr->nucleotide_based_) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeGenomicElementType): in nucleotide-based models, initializeGenomicElementType() requires all mutation types for the genomic element type to be nucleotide-based. Non-nucleotide-based mutation types may be used in nucleotide-based models, but they cannot be autogenerated by SLiM, and therefore cannot be referenced by a genomic element type." << EidosTerminate(); + int mut_type_id_count = mutationTypes_value->Count(); + int proportion_count = proportions_value->Count(); - mutation_types.emplace_back(mutation_type_ptr); - mutation_fractions.emplace_back(proportion); + if (mut_type_id_count != proportion_count) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeGenomicElementType): initializeGenomicElementType() requires the sizes of mutationTypes and proportions to be equal." << EidosTerminate(); - // check whether we are using a mutation type that is non-neutral; check and set pure_neutral_ - if (!mutation_type_ptr->all_neutral_DES_) - pure_neutral_ = false; + for (int mut_type_index = 0; mut_type_index < mut_type_id_count; ++mut_type_index) + { + MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutationTypes_value, mut_type_index, &community_, this, "initializeGenomicElementType()"); // SPECIES CONSISTENCY CHECK + double proportion = proportions_value->NumericAtIndex_NOCAST(mut_type_index, nullptr); + + if ((proportion < 0) || !std::isfinite(proportion)) // == 0 is allowed but the muttype will not be added; see also InitializeDraws() + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeGenomicElementType): initializeGenomicElementType() proportions must be greater than or equal to zero (" << EidosStringForFloat(proportion) << " supplied)." << EidosTerminate(); + + if (std::find(mutation_types.begin(), mutation_types.end(), mutation_type_ptr) != mutation_types.end()) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeGenomicElementType): initializeGenomicElementType() mutation type m" << mutation_type_ptr->mutation_type_id_ << " used more than once." << EidosTerminate(); + + if (nucleotide_based_ && !mutation_type_ptr->nucleotide_based_) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeGenomicElementType): in nucleotide-based models, initializeGenomicElementType() requires all mutation types for the genomic element type to be nucleotide-based. Non-nucleotide-based mutation types may be used in nucleotide-based models, but they cannot be autogenerated by SLiM, and therefore cannot be referenced by a genomic element type." << EidosTerminate(); + + // BCH 1/4/2026: new policy: if the muttype's proportion is zero, we don't really consider it to be used. + // This allows our decisions about whether the simulation is neutral or not to be more accurate. + if (proportion > 0.0) + { + mutation_types.emplace_back(mutation_type_ptr); + mutation_fractions.emplace_back(proportion); + + // let the mutation type know that it is being used + mutation_type_ptr->used_in_GEType_ = true; + + // check whether the mutation type is non-neutral; if so, the species is now considered non-neutral + // (because we expect that a non-neutral mutation will be generated by this genomic element type) + // see also ExecuteMethod_setMutationFractions() and ExecuteMethod_setEffectDistributionForTrait() + if (!mutation_type_ptr->all_neutral_DES_) + species_all_neutral_mutations_ = false; + } + } } EidosValueType mm_type = mutationMatrix_value->Type(); @@ -566,15 +578,29 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const { output_stream << "initializeGenomicElementType(" << map_identifier; - output_stream << ((mut_type_id_count > 1) ? ", c(" : ", "); - for (int mut_type_index = 0; mut_type_index < mut_type_id_count; ++mut_type_index) - output_stream << (mut_type_index > 0 ? ", m" : "m") << mutation_types[mut_type_index]->mutation_type_id_; - output_stream << ((mut_type_id_count > 1) ? ")" : ""); + if (mutation_types.size() == 0) + output_stream << ", integer(0)"; + else { + bool first_mut_type = true; + output_stream << ((mutation_types.size() > 1) ? ", c(" : ", "); + for (MutationType *mut_type : mutation_types) { + output_stream << (first_mut_type ? "m" : ", m") << mut_type->mutation_type_id_; + first_mut_type = false; + } + output_stream << ((mutation_types.size() > 1) ? ")" : ""); + } - output_stream << ((mut_type_id_count > 1) ? ", c(" : ", "); - for (int mut_type_index = 0; mut_type_index < mut_type_id_count; ++mut_type_index) - output_stream << (mut_type_index > 0 ? ", " : "") << proportions_value->NumericAtIndex_NOCAST(mut_type_index, nullptr); - output_stream << ((mut_type_id_count > 1) ? ")" : ""); + if (mutation_fractions.size() == 0) + output_stream << ", integer(0)"; + else { + bool first_mut_fraction = true; + output_stream << ((mutation_fractions.size() > 1) ? ", c(" : ", "); + for (double mut_fraction : mutation_fractions) { + output_stream << (first_mut_fraction ? "" : ", ") << mut_fraction; + first_mut_fraction = false; + } + output_stream << ((mutation_fractions.size() > 1) ? ")" : ""); + } output_stream << ");" << std::endl; } @@ -2858,14 +2884,8 @@ EidosValue_SP Species::ExecuteMethod_addPatternForRecombinant(EidosGlobalStringI EidosValue_SP Species::ExecuteMethod_addSubpop(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) - SLiMCycleStage cycle_stage = community_.CycleStage(); - // TIMING RESTRICTION - if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kWFStage1ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kWFStage5ExecuteLateScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage6ExecuteLateScripts)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_addSubpop): addSubpop() may only be called from a first(), early(), or late() event." << EidosTerminate(); - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_addSubpop): addSubpop() may not be called from inside a callback." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Species::ExecuteMethod_addSubpop", "addSubpop()", ""); EidosValue *subpopID_value = p_arguments[0].get(); EidosValue *size_value = p_arguments[1].get(); @@ -2914,14 +2934,8 @@ EidosValue_SP Species::ExecuteMethod_addSubpopSplit(EidosGlobalStringID p_method if (model_type_ == SLiMModelType::kModelTypeNonWF) EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_addSubpopSplit): addSubpopSplit() is not available in nonWF models." << EidosTerminate(); - SLiMCycleStage cycle_stage = community_.CycleStage(); - // TIMING RESTRICTION - if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kWFStage1ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kWFStage5ExecuteLateScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage6ExecuteLateScripts)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_addSubpopSplit): addSubpopSplit() may only be called from a first(), early(), or late() event." << EidosTerminate(); - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_addSubpopSplit): addSubpopSplit() may not be called from inside a callback." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Species::ExecuteMethod_addSubpopSplit", "addSubpopSplit()", ""); EidosValue *subpopID_value = p_arguments[0].get(); EidosValue *size_value = p_arguments[1].get(); @@ -3251,8 +3265,7 @@ EidosValue_SP Species::ExecuteMethod_killIndividuals(EidosGlobalStringID p_metho // TIMING RESTRICTION if (community_.executing_species_ == this) - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_killIndividuals): killIndividuals() must be called directly from a first(), early(), or late() event, when called on the currently executing species." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Subpopulation::ExecuteMethod_killIndividuals", "killIndividuals()", ", when called on the currently executing species"); EidosValue_Object *individuals_value = (EidosValue_Object *)p_arguments[0].get(); int individuals_count = individuals_value->Count(); @@ -3906,24 +3919,12 @@ EidosValue_SP Species::ExecuteMethod_outputMutations(EidosGlobalStringID p_metho EidosValue_SP Species::ExecuteMethod_readFromPopulationFile(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) - SLiMCycleStage cycle_stage = community_.CycleStage(); - // TIMING RESTRICTION // readFromPopulationFile() is strictly limited to first()/early()/late() events; it cannot be called // from other contexts even for a different species than executing_species_. This is because // it can have the side effect of running mutationEffect() callbacks, and those cannot nest inside // the execution of a different species. - if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && - (cycle_stage != SLiMCycleStage::kWFStage1ExecuteEarlyScripts) && - (cycle_stage != SLiMCycleStage::kWFStage5ExecuteLateScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage6ExecuteLateScripts)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_readFromPopulationFile): readFromPopulationFile() may only be called from a first(), early(), or late() event." << EidosTerminate(); - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && - (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && - (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_readFromPopulationFile): readFromPopulationFile() may not be called from inside a callback." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Species::ExecuteMethod_readFromPopulationFile", "readFromPopulationFile()", ""); if (!community_.warned_early_read_) { @@ -3986,18 +3987,14 @@ EidosValue_SP Species::ExecuteMethod_readFromPopulationFile(EidosGlobalStringID EidosValue_SP Species::ExecuteMethod_recalculateFitness(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) - SLiMCycleStage cycle_stage = community_.CycleStage(); - // TIMING RESTRICTION // recalculateFitness() is strictly limited to first()/early()/late() events; it cannot be called // from other contexts even for a different species than executing_species_. This is because // it can have the side effect of running mutationEffect() callbacks, and those cannot nest inside // the execution of a different species. - if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kWFStage1ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kWFStage5ExecuteLateScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage6ExecuteLateScripts)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_recalculateFitness): recalculateFitness() may only be called from a first(), early(), or late() event." << EidosTerminate(); - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_recalculateFitness): recalculateFitness() may not be called from inside a callback." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Species::ExecuteMethod_recalculateFitness", "recalculateFitness()", ""); + if (InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_recalculateFitness): recalculateFitness() cannot be called when trait/fitness calculation is already underway." << EidosTerminate(); EidosValue *tick_value = p_arguments[0].get(); slim_tick_t tick = (tick_value->Type() != EidosValueType::kValueNULL) ? SLiMCastToTickTypeOrRaise(tick_value->IntAtIndex_NOCAST(0, nullptr)) : community_.Tick(); @@ -4009,6 +4006,9 @@ EidosValue_SP Species::ExecuteMethod_recalculateFitness(EidosGlobalStringID p_me EidosValue *forceRecalc_value = p_arguments[1].get(); eidos_logical_t forceRecalc = forceRecalc_value->LogicalAtIndex_NOCAST(0, nullptr); + // mark that we are doing trait calculations, to block changes to callbacks in use + SetInsideTraitOrFitnessCalculation(true); + // Trigger a fitness recalculation. This is suggested after making a change that would modify fitness values, such as altering // a selection coefficient or dominance coefficient, changing the mutation type for a mutation, etc. It will have the side // effect of calling mutationEffect() callbacks, so this is quite a heavyweight operation. @@ -4017,6 +4017,9 @@ EidosValue_SP Species::ExecuteMethod_recalculateFitness(EidosGlobalStringID p_me // Remember that we have recalculated fitness values; this unlocks the ability to call cachedFitness(), temporarily has_recalculated_fitness_ = true; + // done with trait calculations, unblock + SetInsideTraitOrFitnessCalculation(false); + return gStaticEidosValueVOID; } @@ -4025,6 +4028,14 @@ EidosValue_SP Species::ExecuteMethod_recalculateFitness(EidosGlobalStringID p_me EidosValue_SP Species::ExecuteMethod_registerFitnessEffectCallback(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerFitnessEffectCallback): fitnessEffect() callback script blocks may not be registered within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + if (Active() && ((community_.cycle_stage_ == SLiMCycleStage::kWFStage6CalculateFitness) || (community_.cycle_stage_ == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerFitnessEffectCallback): fitnessEffect() callback script blocks may not be registered during the fitness recalculation tick cycle stage." << EidosTerminate(); + EidosValue *id_value = p_arguments[0].get(); EidosValue *source_value = p_arguments[1].get(); EidosValue *subpop_value = p_arguments[2].get(); @@ -4175,6 +4186,14 @@ EidosValue_SP Species::ExecuteMethod_registerMutationCallback(EidosGlobalStringI EidosValue_SP Species::ExecuteMethod_registerMutationEffectCallback(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerMutationEffectCallback): mutationEffect() callback script blocks may not be registered within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + if (Active() && ((community_.cycle_stage_ == SLiMCycleStage::kWFStage6CalculateFitness) || (community_.cycle_stage_ == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerMutationEffectCallback): mutationEffect() callback script blocks may not be registered during the fitness recalculation tick cycle stage." << EidosTerminate(); + EidosValue *id_value = p_arguments[0].get(); EidosValue *source_value = p_arguments[1].get(); EidosValue *mutType_value = p_arguments[2].get(); @@ -4293,11 +4312,8 @@ EidosValue_SP Species::ExecuteMethod_simulationFinished(EidosGlobalStringID p_me EidosValue_SP Species::ExecuteMethod_skipTick(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) - SLiMCycleStage cycle_stage = community_.CycleStage(); - // TIMING RESTRICTION - if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_skipTick): skipTick() may only be called from a first() event; skipping ticks should be arranged before any portion of the cycle has occurred." << EidosTerminate(); + community_.EnforceTimingRestriction_FirstEventStageOnly("Species::ExecuteMethod_skipTick", "skipTick()", "; skipping ticks should be arranged before any portion of the cycle has occurred"); if (species_active_) { @@ -4570,14 +4586,8 @@ EidosValue_SP Species::ExecuteMethod_treeSeqSimplify(EidosGlobalStringID p_metho if (!recording_tree_) EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_treeSeqSimplify): treeSeqSimplify() may only be called when tree recording is enabled." << EidosTerminate(); - SLiMCycleStage cycle_stage = community_.CycleStage(); - // TIMING RESTRICTION - if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kWFStage1ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kWFStage5ExecuteLateScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage6ExecuteLateScripts)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_treeSeqSimplify): treeSeqSimplify() may only be called from a first(), early(), or late() event." << EidosTerminate(); - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_treeSeqSimplify): treeSeqSimplify() may not be called from inside a callback." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Species::ExecuteMethod_treeSeqSimplify", "treeSeqSimplify()", ""); SimplifyAllTreeSequences(); @@ -4644,13 +4654,7 @@ EidosValue_SP Species::ExecuteMethod_treeSeqOutput(EidosGlobalStringID p_method_ SLiMCycleStage cycle_stage = community_.CycleStage(); // TIMING RESTRICTION - if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kWFStage1ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kWFStage5ExecuteLateScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage6ExecuteLateScripts)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_treeSeqOutput): treeSeqOutput() may only be called from a first(), early(), or late() event." << EidosTerminate(); - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && - (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && - (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_treeSeqOutput): treeSeqOutput() may not be called from inside a callback." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Species::ExecuteMethod_treeSeqOutput", "treeSeqOutput()", ""); std::string path_string = path_value->StringAtIndex_NOCAST(0, nullptr); bool simplify = simplify_value->LogicalAtIndex_NOCAST(0, nullptr); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 171ca3a1..2a3c2531 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1783,6 +1783,7 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati for (SLiMEidosBlock *mutationEffect_callback : p_mutationEffect_callbacks) { + // FIXME MULTITRAIT: I think this check will no longer be necessary, once my overhaul of optimization flags is complete; p_mutationEffect_callbacks will only contain active callbacks in the first place! if (mutationEffect_callback->block_active_) { slim_objectid_t callback_mutation_type_id = mutationEffect_callback->mutation_type_id_; @@ -1959,6 +1960,7 @@ slim_fitness_t Subpopulation::ApplyFitnessEffectCallbacks(std::vectorblock_active_) { #if DEBUG_POINTS_ENABLED @@ -5737,10 +5739,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_addCloned(EidosGlobalStringID p_metho EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addCloned): addCloned() is not available in WF models." << EidosTerminate(); // TIMING RESTRICTION - if (community_.CycleStage() != SLiMCycleStage::kNonWFStage1GenerateOffspring) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addCloned): addCloned() may only be called from a reproduction() callback." << EidosTerminate(); - if (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosReproductionCallback) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addCloned): addCloned() may not be called from a nested callback." << EidosTerminate(); + community_.EnforceTimingRestriction_ReproductionCallbackOnly("Subpopulation::ExecuteMethod_addCloned", "addCloned()", ""); // Get and check the parent EidosValue *parent_value = p_arguments[0].get(); @@ -5827,10 +5826,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_addCrossed(EidosGlobalStringID p_meth EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addCrossed): addCrossed() is not available in WF models." << EidosTerminate(); // TIMING RESTRICTION - if (community_.CycleStage() != SLiMCycleStage::kNonWFStage1GenerateOffspring) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addCrossed): addCrossed() may only be called from a reproduction() callback." << EidosTerminate(); - if (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosReproductionCallback) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addCrossed): addCrossed() may not be called from a nested callback." << EidosTerminate(); + community_.EnforceTimingRestriction_ReproductionCallbackOnly("Subpopulation::ExecuteMethod_addCrossed", "addCrossed()", ""); // Get and check the first parent (the mother) EidosValue *parent1_value = p_arguments[0].get(); @@ -5942,10 +5938,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_addEmpty(EidosGlobalStringID p_method EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addEmpty): addEmpty() is not available in WF models." << EidosTerminate(); // TIMING RESTRICTION - if (community_.CycleStage() != SLiMCycleStage::kNonWFStage1GenerateOffspring) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addEmpty): addEmpty() may only be called from a reproduction() callback." << EidosTerminate(); - if (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosReproductionCallback) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addEmpty): addEmpty() may not be called from a nested callback." << EidosTerminate(); + community_.EnforceTimingRestriction_ReproductionCallbackOnly("Subpopulation::ExecuteMethod_addEmpty", "addEmpty()", ""); // Check the count and short-circuit if it is zero EidosValue *count_value = p_arguments[3].get(); @@ -6018,10 +6011,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_addMultiRecombinant(EidosGlobalString EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addMultiRecombinant): addMultiRecombinant() is not available in WF models." << EidosTerminate(); // TIMING RESTRICTION - if (community_.CycleStage() != SLiMCycleStage::kNonWFStage1GenerateOffspring) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addMultiRecombinant): addMultiRecombinant() may only be called from a reproduction() callback." << EidosTerminate(); - if (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosReproductionCallback) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addMultiRecombinant): addMultiRecombinant() may not be called from a nested callback." << EidosTerminate(); + community_.EnforceTimingRestriction_ReproductionCallbackOnly("Subpopulation::ExecuteMethod_addMultiRecombinant", "addMultiRecombinant()", ""); // We could technically make this work in the no-genetics case, if the parameters specify that both // child haplosomes are null, but there's really no reason for anybody to use addRecombinant() in that @@ -6887,10 +6877,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_addRecombinant(EidosGlobalStringID p_ EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addRecombinant): addRecombinant() is not available in WF models." << EidosTerminate(); // TIMING RESTRICTION - if (community_.CycleStage() != SLiMCycleStage::kNonWFStage1GenerateOffspring) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addRecombinant): addRecombinant() may only be called from a reproduction() callback." << EidosTerminate(); - if (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosReproductionCallback) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addRecombinant): addRecombinant() may not be called from a nested callback." << EidosTerminate(); + community_.EnforceTimingRestriction_ReproductionCallbackOnly("Subpopulation::ExecuteMethod_addRecombinant", "addRecombinant()", ""); // We could technically make this work in the no-genetics case, if the parameters specify that both // child haplosomes are null, but there's really no reason for anybody to use addRecombinant() in that @@ -7479,10 +7466,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_addSelfed(EidosGlobalStringID p_metho EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addSelfed): addSelfed() is not available in WF models." << EidosTerminate(); // TIMING RESTRICTION - if (community_.CycleStage() != SLiMCycleStage::kNonWFStage1GenerateOffspring) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addSelfed): addSelfed() may only be called from a reproduction() callback." << EidosTerminate(); - if (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosReproductionCallback) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_addSelfed): addSelfed() may not be called from a nested callback." << EidosTerminate(); + community_.EnforceTimingRestriction_ReproductionCallbackOnly("Subpopulation::ExecuteMethod_addSelfed", "addSelfed()", ""); // Get and check the parent EidosValue *parent_value = p_arguments[0].get(); @@ -7570,8 +7554,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_takeMigrants(EidosGlobalStringID p_me // TIMING RESTRICTION if (community_.executing_species_ == &species_) - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_takeMigrants): takeMigrants() must be called directly from a first(), early(), or late() event, when called on the currently executing species." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Subpopulation::ExecuteMethod_takeMigrants", "takeMigrants()", ", when called on the currently executing species"); EidosValue_Object *migrants_value = (EidosValue_Object *)p_arguments[0].get(); int migrant_count = migrants_value->Count(); @@ -7824,11 +7807,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_deviatePositions(EidosGlobalStringID SLiMCycleStage cycle_stage = community_.CycleStage(); // TIMING RESTRICTION - if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kWFStage1ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kWFStage5ExecuteLateScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage6ExecuteLateScripts)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_deviatePositions): deviatePositions() may only be called from a first(), early(), or late() event." << EidosTerminate(); - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_deviatePositions): deviatePositions() may not be called from inside a callback." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Subpopulation::ExecuteMethod_deviatePositions", "deviatePositions()", ""); int dimensionality = species_.SpatialDimensionality(); @@ -8249,11 +8228,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_deviatePositionsWithMap(EidosGlobalSt SLiMCycleStage cycle_stage = community_.CycleStage(); // TIMING RESTRICTION - if ((cycle_stage != SLiMCycleStage::kWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kWFStage1ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kWFStage5ExecuteLateScripts) && - (cycle_stage != SLiMCycleStage::kNonWFStage0ExecuteFirstScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts) && (cycle_stage != SLiMCycleStage::kNonWFStage6ExecuteLateScripts)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_deviatePositionsWithMap): deviatePositionsWithMap() may only be called from a first(), early(), or late() event." << EidosTerminate(); - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_deviatePositionsWithMap): deviatePositionsWithMap() may not be called from inside a callback." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Subpopulation::ExecuteMethod_deviatePositionsWithMap", "deviatePositionsWithMap()", ""); int dimensionality = species_.SpatialDimensionality(); @@ -10232,9 +10207,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_removeSubpopulation(EidosGlobalString EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_removeSubpopulation): removeSubpopulation() is not available in WF models." << EidosTerminate(); // TIMING RESTRICTION - if (community_.executing_species_ == &species_) - if ((community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventFirst) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventEarly) && (community_.executing_block_type_ != SLiMEidosBlockType::SLiMEidosEventLate)) - EIDOS_TERMINATION << "ERROR (Subpopulation::ExecuteMethod_removeSubpopulation): removeSubpopulation() must be called directly from a first(), early(), or late() event, when called on the currently executing species." << EidosTerminate(); + community_.EnforceTimingRestriction_EventBlockOnly("Subpopulation::ExecuteMethod_removeSubpopulation", "removeSubpopulation()", ""); population_.RemoveSubpopulation(*this); From e5e500ee74c3ef57ec12a62ab02dc5be154c3021 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 5 Jan 2026 17:29:42 -0600 Subject: [PATCH 068/107] fix CI build failure --- core/species_eidos.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 99f56bfc..75ae0716 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -4033,7 +4033,7 @@ EidosValue_SP Species::ExecuteMethod_registerFitnessEffectCallback(EidosGlobalSt // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu if (InsideTraitOrFitnessCalculation()) EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerFitnessEffectCallback): fitnessEffect() callback script blocks may not be registered within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); - if (Active() && ((community_.cycle_stage_ == SLiMCycleStage::kWFStage6CalculateFitness) || (community_.cycle_stage_ == SLiMCycleStage::kNonWFStage3CalculateFitness))) + if (Active() && ((community_.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community_.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerFitnessEffectCallback): fitnessEffect() callback script blocks may not be registered during the fitness recalculation tick cycle stage." << EidosTerminate(); EidosValue *id_value = p_arguments[0].get(); @@ -4191,7 +4191,7 @@ EidosValue_SP Species::ExecuteMethod_registerMutationEffectCallback(EidosGlobalS // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu if (InsideTraitOrFitnessCalculation()) EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerMutationEffectCallback): mutationEffect() callback script blocks may not be registered within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); - if (Active() && ((community_.cycle_stage_ == SLiMCycleStage::kWFStage6CalculateFitness) || (community_.cycle_stage_ == SLiMCycleStage::kNonWFStage3CalculateFitness))) + if (Active() && ((community_.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community_.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerMutationEffectCallback): mutationEffect() callback script blocks may not be registered during the fitness recalculation tick cycle stage." << EidosTerminate(); EidosValue *id_value = p_arguments[0].get(); From c2a9c891e68001695d8416c16627e896d128aa4c Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Tue, 6 Jan 2026 15:56:37 -0600 Subject: [PATCH 069/107] more optimization flag cleanup --- core/chromosome.cpp | 10 -- core/community.cpp | 3 + core/genomic_element_type.cpp | 9 +- core/haplosome.cpp | 40 ++------ core/individual.cpp | 11 +-- core/mutation.cpp | 119 +++++++++++++----------- core/mutation.h | 23 ++--- core/mutation_run.cpp | 6 +- core/mutation_type.cpp | 40 ++------ core/mutation_type.h | 4 - core/population.cpp | 14 +-- core/species.cpp | 168 ++++++++++++++++++++-------------- core/species.h | 42 ++++++++- core/species_eidos.cpp | 17 ---- core/subpopulation.cpp | 4 - core/substitution.cpp | 26 ++++-- core/substitution.h | 23 ++--- 17 files changed, 276 insertions(+), 283 deletions(-) diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 1b460f8e..6241a77e 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1045,11 +1045,6 @@ MutationIndex Chromosome::DrawNewMutation(std::pairis_neutral_) - species_.NoteNonNeutralMutation(mutation); - return new_mut_index; } @@ -1447,11 +1442,6 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pairis_neutral_) - species_.NoteNonNeutralMutation(mutation); - return new_mut_index; } diff --git a/core/community.cpp b/core/community.cpp index da6d297e..2673cd1b 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -2610,6 +2610,9 @@ void Community::AllSpecies_CheckIntegrity(void) // Check the integrity of all substitution objects for (Substitution *sub : species->population_.substitutions_) sub->SelfConsistencyCheck(" in AllSpecies_CheckIntegrity()"); + + // Check the integrity of Species optimization flags + species->CheckOptimizationFlags(); } #endif } diff --git a/core/genomic_element_type.cpp b/core/genomic_element_type.cpp index e2868462..8f175932 100644 --- a/core/genomic_element_type.cpp +++ b/core/genomic_element_type.cpp @@ -345,8 +345,8 @@ void GenomicElementType::SetProperty(EidosGlobalStringID p_property_id, const Ei if (!color_.empty()) Eidos_GetColorComponents(color_, &color_red_, &color_green_, &color_blue_); - // tweak a flag to make SLiMgui update - species_.community_.genomic_element_types_changed_ = true; + // let the species know that our configuration has changed + species_.AutogenerationConfigurationChanged(); return; } @@ -410,9 +410,6 @@ EidosValue_SP GenomicElementType::ExecuteMethod_setMutationFractions(EidosGlobal mutation_types.emplace_back(mutation_type_ptr); mutation_fractions.emplace_back(proportion); - // let the mutation type know that it is being used - mutation_type_ptr->used_in_GEType_ = true; - // check whether the mutation type is non-neutral; if so, the species is now considered non-neutral // (because we expect that a non-neutral mutation will be generated by this genomic element type) // see also Species::ExecuteContextFunction_initializeGenomicElementType() for the same logic @@ -429,7 +426,7 @@ EidosValue_SP GenomicElementType::ExecuteMethod_setMutationFractions(EidosGlobal InitializeDraws(); // Notify interested parties of the change - species_.community_.genomic_element_types_changed_ = true; + species_.AutogenerationConfigurationChanged(); return gStaticEidosValueVOID; } diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 6a4ea627..b4a3e3b4 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2583,9 +2583,8 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addMutations(EidosGlobalStringID p_ // No need to add the mutation to the registry; how would the user ever get a Mutation that was not already in it? - // BCH 1/4/2025: the mutation is already in the system, so for now we don't need to note it - //if (!mut_to_add->is_neutral_) - // species->NoteNonNeutralMutation(mut_to_add); + // BCH 1/4/2025: the mutation is already in the system, so its effects have been + // noted; no call to NoteChangedMutation(mut_to_add) is needed } } } @@ -2964,10 +2963,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID if (p_method_id == gID_addNewDrawnMutation) { new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, origin_subpop_id, origin_tick, (int8_t)nucleotide); - - // this mutation will be added to the simulation somewhere, so tell the species about it - if (!new_mut->is_neutral_) - species->NoteNonNeutralMutation(new_mut); } else // (p_method_id == gID_addNewMutation) { @@ -2984,10 +2979,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... and hemizygous dominance... // FIXME MULTITRAIT this code will also now need to handle the independent dominance case new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); - - // this mutation will be added to the simulation somewhere, so tell the species about it - if (!new_mut->is_neutral_) - species->NoteNonNeutralMutation(new_mut); } // add to the registry, return value, haplosome, etc. @@ -3484,10 +3475,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr // FIXME MULTITRAIT this code will also now need to handle the independent dominance case Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); - // this mutation will be added to the simulation somewhere, so tell the species about it - if (!new_mut->is_neutral_) - species.NoteNonNeutralMutation(new_mut); - // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry pop.MutationRegistryAdd(new_mut); mutation_indices.emplace_back(new_mut_index); @@ -4136,10 +4123,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } - // this mutation will be added to the simulation somewhere, so tell the species about it - if (!new_mut->is_neutral_) - species->NoteNonNeutralMutation(new_mut); - // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry pop.MutationRegistryAdd(new_mut); alt_allele_mut_indices.emplace_back(new_mut_index); @@ -4391,7 +4374,6 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID // Construct a vector of mutations to remove that is sorted by position std::vector mutations_to_remove; Mutation * const *mutations_data = (Mutation * const *)mutations_value->ObjectData(); - slim_trait_index_t trait_count = species->TraitCount(); for (int value_index = 0; value_index < mutations_count; ++value_index) { @@ -4405,20 +4387,12 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID mutations_to_remove.emplace_back(mut); + // Note the removal; it might affect our caching, even if the mutation is neutral + species->NoteChangedMutation(mut); + // If we're not already aware of having removed a non-neutral mutation, check on that now - if (!any_nonneutral_removed) - { - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); - - for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) - { - if (mut_trait_info[trait_index].effect_size_ != (slim_effect_t)0.0) - { - any_nonneutral_removed = true; - break; - } - } - } + if (!mut->is_neutral_for_all_traits_) + any_nonneutral_removed = true; } std::sort(mutations_to_remove.begin(), mutations_to_remove.end(), [ ](Mutation *i1, Mutation *i2) {return i1->position_ < i2->position_;}); diff --git a/core/individual.cpp b/core/individual.cpp index aaead750..06708fa9 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -1430,7 +1430,7 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) } Subpopulation *subpop = subpopulation_; - double fitness = subpop->individual_cached_fitness_OVERRIDE_ ? subpop->individual_cached_fitness_OVERRIDE_value_ : cached_fitness_UNSAFE_; + double fitness = subpop->individual_cached_fitness_OVERRIDE_ ? subpop->individual_cached_fitness_OVERRIDE_value_ : (double)cached_fitness_UNSAFE_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(fitness)); } @@ -1955,7 +1955,7 @@ EidosValue *Individual::GetProperty_Accelerated_cachedFitness(EidosGlobalStringI { Individual *ind = (Individual *)(p_values[value_index]); - float_result->set_float_no_check(ind->cached_fitness_UNSAFE_, value_index); + float_result->set_float_no_check((double)ind->cached_fitness_UNSAFE_, value_index); } } } @@ -1965,7 +1965,7 @@ EidosValue *Individual::GetProperty_Accelerated_cachedFitness(EidosGlobalStringI { Individual *ind = (Individual *)(p_values[value_index]); Subpopulation *subpop = ind->subpopulation_; - double fitness = (subpop->individual_cached_fitness_OVERRIDE_ ? subpop->individual_cached_fitness_OVERRIDE_value_ : ind->cached_fitness_UNSAFE_); + double fitness = (subpop->individual_cached_fitness_OVERRIDE_ ? subpop->individual_cached_fitness_OVERRIDE_value_ : (double)ind->cached_fitness_UNSAFE_); float_result->set_float_no_check(fitness, value_index); } @@ -5493,6 +5493,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if (info_mutids.size() > 0) { // a mutation ID was supplied; we use it blindly, having checked above that we are in the case where this is legal + // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... slim_mutationid_t mut_mutid = info_mutids[alt_allele_index]; new_mut = new (mut_block_ptr + new_mut_index) Mutation(mut_mutid, mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); @@ -5504,10 +5505,6 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), mut_position, selection_coeff, dominance_coeff, subpop_index, origin_tick, nucleotide); } - // all mutations seen here will be added to the simulation somewhere, so tell the species about it - if (!new_mut->is_neutral_) - species->NoteNonNeutralMutation(new_mut); - // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry pop.MutationRegistryAdd(new_mut); alt_allele_mut_indices.emplace_back(new_mut_index); diff --git a/core/mutation.cpp b/core/mutation.cpp index 294107e1..a17d1be9 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -65,7 +65,8 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. - is_neutral_ = true; // will be set to false below as needed + is_neutral_for_all_traits_ = true; // will be set to false below as needed + is_neutral_for_direct_fitness_traits_ = true; // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits is_independent_dominance_ = std::isnan(p_dominance_coeff); @@ -88,7 +89,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (effect != (slim_effect_t)0.0) { - is_neutral_ = false; + is_neutral_for_all_traits_ = false; + + if (trait->HasDirectFitnessEffect()) + is_neutral_for_direct_fitness_traits_ = false; // get the realized dominance to handle the possibility of independent dominance slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -125,13 +129,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // this mutation will be added to the simulation somewhere, so tell the species about it // (OK, it might not get added due to stacking policy or mutation() callbacks, but we assume it will be) - if (!is_neutral_) - { - species.NoteNonNeutralMutation(this); - - // FIXME MULTITRAIT maybe this should get managed by NoteNonNeutralMutation() as well? seems like part of its duties... - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! - } + species.NoteChangedMutation(this); #if DEBUG SelfConsistencyCheck(" in Mutation::Mutation()"); @@ -171,7 +169,8 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. - is_neutral_ = true; // will be set to false below as needed + is_neutral_for_all_traits_ = true; // will be set to false below as needed + is_neutral_for_direct_fitness_traits_ = true; // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits is_independent_dominance_ = std::isnan(mutation_type_ptr_->DefaultDominanceForTrait(0)); @@ -225,7 +224,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (effect != (slim_effect_t)0.0) { - is_neutral_ = false; + is_neutral_for_all_traits_ = false; + + if (trait->HasDirectFitnessEffect()) + is_neutral_for_direct_fitness_traits_ = false; // get the realized dominance to handle the possibility of independent dominance slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -263,13 +265,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // this mutation will be added to the simulation somewhere, so tell the species about it // (OK, it might not get added due to stacking policy or mutation() callbacks, but we assume it will be) - if (!is_neutral_) - { - species.NoteNonNeutralMutation(this); - - // FIXME MULTITRAIT maybe this should get managed by NoteNonNeutralMutation() as well? seems like part of its duties... - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! - } + species.NoteAutogeneratedMutation(this); #if DEBUG SelfConsistencyCheck(" in Mutation::Mutation()"); @@ -305,7 +301,8 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. - is_neutral_ = true; // will be set to false below as needed + is_neutral_for_all_traits_ = true; // will be set to false below as needed + is_neutral_for_direct_fitness_traits_ = true; // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits is_independent_dominance_ = std::isnan(p_dominance_coeff); @@ -328,7 +325,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (effect != (slim_effect_t)0.0) { - is_neutral_ = false; + is_neutral_for_all_traits_ = false; + + if (trait->HasDirectFitnessEffect()) + is_neutral_for_direct_fitness_traits_ = false; // get the realized dominance to handle the possibility of independent dominance slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -365,13 +365,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // this mutation will be added to the simulation somewhere, so tell the species about it // (OK, it might not get added due to stacking policy or mutation() callbacks, but we assume it will be) - if (!is_neutral_) - { - species.NoteNonNeutralMutation(this); - - // FIXME MULTITRAIT maybe this should get managed by NoteNonNeutralMutation() as well? seems like part of its duties... - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! - } + species.NoteChangedMutation(this); #if DEBUG SelfConsistencyCheck(" in Mutation::Mutation()"); @@ -405,6 +399,7 @@ void Mutation::SelfConsistencyCheck(const std::string &p_message_end) const std::vector &traits = species.Traits(); slim_trait_index_t trait_count = species.TraitCount(); bool all_neutral_effects = true; + bool all_neutral_direct_effects = true; for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { @@ -448,11 +443,21 @@ void Mutation::SelfConsistencyCheck(const std::string &p_message_end) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) hemizygous_effect_ does not match expectations" << p_message_end << "." << EidosTerminate(); if (traitInfoRec.effect_size_ != (slim_effect_t)0.0) + { all_neutral_effects = false; + + if (trait->HasDirectFitnessEffect()) + all_neutral_direct_effects = false; + } } - if ((is_neutral_ && !all_neutral_effects) || (!is_neutral_ && all_neutral_effects)) + if ((is_neutral_for_all_traits_ && !all_neutral_effects) || + (!is_neutral_for_all_traits_ && all_neutral_effects)) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation neutrality state is inconsistent" << p_message_end << "." << EidosTerminate(); + + if ((is_neutral_for_direct_fitness_traits_ && !all_neutral_direct_effects) || + (!is_neutral_for_direct_fitness_traits_ && all_neutral_direct_effects)) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation direct-effect neutrality state is inconsistent" << p_message_end << "." << EidosTerminate(); } slim_effect_t Mutation::RealizedDominanceForTrait(Trait *p_trait) @@ -528,31 +533,35 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * p_new_effect); // 2ha (using h_hemi) } - // This mutation is no longer neutral; various observers care about that change - Species &species = mutation_type_ptr_->species_; - - is_neutral_ = false; - species.NoteNonNeutralMutation(this); + is_neutral_for_all_traits_ = false; - // FIXME MULTITRAIT maybe this should get managed by NoteNonNeutralMutation() as well? seems like part of its duties... - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags + if (p_trait->HasDirectFitnessEffect()) + is_neutral_for_direct_fitness_traits_ = false; } else // p_new_effect == 0.0; therefore, old_effect != 0.0 { // Changing from non-neutral to neutral; determine whether the whole mutation is now neutral // This is a bit complicated, but I don't expect this case to be hit very often Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); slim_trait_index_t trait_count = species.TraitCount(); - is_neutral_ = true; + is_neutral_for_all_traits_ = true; + is_neutral_for_direct_fitness_traits_ = true; for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { - if ((mut_trait_info + trait_index)->effect_size_ != (slim_effect_t)0.0) + MutationTraitInfo &info_for_trait = mut_trait_info[trait_index]; + + if (info_for_trait.effect_size_ != (slim_effect_t)0.0) { - is_neutral_ = false; + is_neutral_for_all_traits_ = false; + + if (traits[trait_index]->HasDirectFitnessEffect()) + is_neutral_for_direct_fitness_traits_ = false; + break; } } @@ -560,8 +569,6 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e // Note that we cannot set species.species_all_neutral_mutations_ and mutation_type_ptr_->all_neutral_mutations_ to // false here, because only this mutation has changed to neutral; other mutations might be non-neutral - species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags - // cache values used by the fitness calculation code for speed; see header // for a neutral trait, we can set up this info very quickly if (p_trait->Type() == TraitType::kMultiplicative) @@ -577,6 +584,11 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e traitInfoRec->hemizygous_effect_ = (slim_effect_t)0.0; } } + + // notify the species that the mutation has changed + Species &species = mutation_type_ptr_->species_; + + species.NoteChangedMutation(this); } // This should be called whenever a mutation dominance is changed; it handles the necessary recaching @@ -592,8 +604,8 @@ void Mutation::SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, sli is_independent_dominance_ = false; // We only need to recache the heterozygous_effect_ value, since only it is affected by the change in - // dominance coefficient. Changing dominance has no effect on is_neutral_ or any of the other is-neutral - // flags. So this is very simple. + // dominance coefficient. Changing dominance has no effect on is_neutral_for_all_traits_ or any of the + // other is-neutral flags. So this is very simple. slim_effect_t effect_size = traitInfoRec->effect_size_; slim_effect_t realized_dominance = RealizedDominanceForTrait(p_trait); @@ -607,9 +619,8 @@ void Mutation::SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, sli traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); } - // when non-neutral mutation characteristics change, the species needs to be notified - if (!is_neutral_) - mutation_type_ptr_->species_.NoteNonNeutralMutation(this); + // when mutation characteristics change, the species needs to be notified + mutation_type_ptr_->species_.NoteChangedMutation(this); } void Mutation::SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) @@ -617,8 +628,8 @@ void Mutation::SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitIn traitInfoRec->hemizygous_dominance_coeff_ = p_new_dominance; // We only need to recache the hemizygous_effect_ values, since only they are affected by the change in - // dominance coefficient. Changing dominance has no effect on is_neutral_ or any of the other is-neutral - // flags. So this is very simple. + // dominance coefficient. Changing dominance has no effect on is_neutral_for_all_traits_ or any of the + // other is-neutral flags. So this is very simple. if (p_trait->Type() == TraitType::kMultiplicative) { @@ -629,9 +640,8 @@ void Mutation::SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitIn traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); } - // when non-neutral mutation characteristics change, the species needs to be notified - if (!is_neutral_) - mutation_type_ptr_->species_.NoteNonNeutralMutation(this); + // when mutation characteristics change, the species needs to be notified + mutation_type_ptr_->species_.NoteChangedMutation(this); } void Mutation::SelfDelete(void) @@ -698,7 +708,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) case gID_isIndependentDominance: // ACCELERATED return (is_independent_dominance_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_isNeutral: // ACCELERATED - return (is_neutral_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + return (is_neutral_for_all_traits_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_isSegregating: // ACCELERATED return ((state_ == MutationState::kInRegistry) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_mutationType: // ACCELERATED @@ -945,7 +955,7 @@ EidosValue *Mutation::GetProperty_Accelerated_isNeutral(EidosGlobalStringID p_pr { Mutation *value = (Mutation *)(p_values[value_index]); - logical_result->set_logical_no_check(value->is_neutral_, value_index); + logical_result->set_logical_no_check(value->is_neutral_for_all_traits_, value_index); } return logical_result; @@ -1387,9 +1397,8 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth // We take just the mutation type pointer; if the user wants a new selection coefficient, they can do that themselves mutation_type_ptr_ = mutation_type_ptr; - // when non-neutral mutation characteristics change, the species needs to be notified - if (!is_neutral_) - mutation_type_ptr_->species_.NoteNonNeutralMutation(this); + // when mutation characteristics change, the species needs to be notified + mutation_type_ptr_->species_.NoteChangedMutation(this); // Changing the mutation type no longer changes the dominance coefficient or the hemizygous dominance // coefficient, so there are no longer any side effects on trait effects / fitness to be managed here. diff --git a/core/mutation.h b/core/mutation.h index 4b0fb44b..6760aac9 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -101,19 +101,16 @@ class Mutation : public EidosDictionaryRetained slim_chromosome_index_t chromosome_index_; // the (uint8_t) index of this mutation's chromosome int state_ : 4; // see MutationState above; 4 bits so we can represent -1 - // is_neutral_ is true if all mutation effects are 0.0 (note this might be overridden by a callback). - // The state of is_neutral_ is updated to reflect the current state of the mutation whenever it changes. + // is_neutral_for_all_traits_ is true if all mutation effects are 0.0 (note a callback might override this). + // The state of is_neutral_for_all_traits_ is updated to reflect the state of the mutation when it changes. // This is used to make constructing non-neutral caches for trait evaluation fast with multiple traits. - unsigned int is_neutral_ : 1; - // FIXME MULTITRAIT: it occurs to me that the present is_neutral_ flag on mutations is ambiguous. One meaning - // is "this mutation has neutral effects for all traits"; such mutations can be disregarded in all phenotype - // calculations. The other is "this mutation has neutral effects for all traits *that have a direct fitness - // effect*"; such mutations can be disregarded for all calculations leading to a fitness value. The former - // is the meaning that needs to determine whether a given mutation is placed into a non-neutral cache, since - // the non-neutral caches will be used for all phenotype calculations (I THINK?). The latter is the meaning - // that should be used to determine whether a given trait with a direct fitness effect is considered to be - // neutral or not; if any mutation has a non-neutral effect on that given trait, then that trait needs to be - // demanded and factored in to fitness calculations. + unsigned int is_neutral_for_all_traits_ : 1; + + // is_neutral_for_direct_fitness_traits_ is all mutation effects for traits with a direct effect on fitness + // are 0.0 (note a callback might override this). The state of is_neutral_for_direct_fitness_traits_ is + // updated to reflect the state of the mutation when it changes. This is used to decide which traits to + // demand and calculate when fitness is being calculated. + unsigned int is_neutral_for_direct_fitness_traits_ : 1; // is_independent_dominance_ is true if the mutation has been configured to exhibit "independent dominance", // meaning that two heterozygous effects equal one homozygous effect, allowing the effects from haplosomes @@ -122,7 +119,7 @@ class Mutation : public EidosDictionaryRetained // but only based upon the special NAN dominance value in setDominanceForTrait(); setting dominance values // that happen to produce independent dominance does not cause this flag to be set, only the special NAN // value. This is used to construct independent-dominance caches for fast trait evaluation. Note that this - // flag can be true when is_neutral_ is also true, recording that independent dominance was configured. + // flag and is_neutral_for_all_traits_ can both be true, recording that independent dominance was configured. unsigned int is_independent_dominance_ : 1; int8_t nucleotide_; // the nucleotide being kept: A=0, C=1, G=2, T=3. -1 is used to indicate non-nucleotide-based. diff --git a/core/mutation_run.cpp b/core/mutation_run.cpp index 8fdfbeed..76a39402 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -458,7 +458,7 @@ void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_hal // MutationIndex mutindex = mutations_[bufindex]; // Mutation *mutptr = p_mut_block_ptr + mutindex; // -// if (!mutptr->is_neutral_) +// if (!mutptr->is_neutral_for_all_traits_) // add_to_nonneutral_buffer(mutindex); // } //} @@ -494,7 +494,7 @@ void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_hal // // The result of && is not order-dependent, but the first condition is checked first. // // I expect many mutations would fail the first test (thus short-circuiting), whereas // // few would fail the second test (i.e. actually be 0.0) in a QTL model. -// if ((!mutptr->mutation_type_ptr_->set_neutral_by_global_active_callback_) && !mutptr->is_neutral_) +// if ((!mutptr->mutation_type_ptr_->set_neutral_by_global_active_callback_) && !mutptr->is_neutral_for_all_traits_) // add_to_nonneutral_buffer(mutindex); // } //} @@ -519,7 +519,7 @@ void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_hal // // // The result of || is not order-dependent, but the first condition is checked first. // // I have reordered this to put the fast test first; or I'm guessing it's the fast test. -// if (!mutptr->is_neutral_ || (mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_)) +// if (!mutptr->is_neutral_for_all_traits_ || (mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_)) // add_to_nonneutral_buffer(mutindex); // } //} diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 16813926..59a503c2 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -941,8 +941,8 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba // BCH 7/2/2025: Changing the default dominance coefficient no longer means that the cached fitness // effects of all mutations using this type become invalid; it is now just the *default* coefficient, // and changing it does not change the state of mutations that have already derived from it. We do - // still want to let the community know that a mutation type has changed, though. - species_.community_.mutation_types_changed_ = true; + // still want to let the species know that a mutation type has changed, though. + species_.AutogenerationConfigurationChanged(); SelfConsistencyCheck(" in setDefaultDominanceForTrait()"); @@ -991,8 +991,8 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( // BCH 7/2/2025: Changing the default dominance coefficient no longer means that the cached fitness // effects of all mutations using this type become invalid; it is now just the *default* coefficient, // and changing it does not change the state of mutations that have already derived from it. We do - // still want to let the community know that a mutation type has changed, though. - species_.community_.mutation_types_changed_ = true; + // still want to let the species know that a mutation type has changed, though. + species_.AutogenerationConfigurationChanged(); SelfConsistencyCheck(" in setDefaultDominanceForTrait()"); @@ -1019,10 +1019,6 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo MutationType::ParseDESParameters(DES_type_string, p_arguments.data() + 2, (int)p_arguments.size() - 2, &DES_type, &DES_parameters, &DES_strings); - // keep track of whether we have ever seen a type 's' (scripted) DES; if so, we switch to a slower case when evolving - if (DES_type == DESType::kScript) - species_.type_s_DESs_present_ = true; - // Everything seems to be in order, so replace our distribution info (in each specified trait) with the new info for (slim_trait_index_t trait_index : trait_indices) { @@ -1033,32 +1029,8 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo DES_info.DES_strings_ = DES_strings; } - // mark that mutation types changed, so they get redisplayed in SLiMgui - species_.community_.mutation_types_changed_ = true; - - // check whether our DES for all traits is now neutral; we can change from non-neutral back to neutral - all_neutral_DES_ = true; - - for (slim_trait_index_t trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) - { - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; - - if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) - all_neutral_DES_ = false; - } - - if (!all_neutral_DES_) - { - // if our DES is non-neutral and we're used in a GenomicElementType, that implies that the simulation is - // now non-neutral (since we expect that a non-neutral mutation will now be generated by the GEType) - // see also Species::ExecuteContextFunction_initializeGenomicElementType() for the same logic - if (used_in_GEType_) - species_.species_all_neutral_mutations_ = false; - - // if our DES is non-neutral, we set muttype_all_neutral_mutations_ to false to track that we are non-neutral - // see also MutationType::MutationType() for the same logic at init time - muttype_all_neutral_mutations_ = false; - } + // notify the species that we have changed + species_.AutogenerationConfigurationChanged(); return gStaticEidosValueVOID; } diff --git a/core/mutation_type.h b/core/mutation_type.h index 0cf8b85d..ac7296ba 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -137,10 +137,6 @@ class MutationType : public EidosDictionaryUnretained // forth. Different parts of the code need to know slightly different things, so we have several different // flags of this sort. The subtle differences between these flags can be crucially important! - // this flag is set if the mutation type is ever used in a GenomicElementType; if a mutation type - // is never used in a GenomicElementType, it does not affect whether the simulation is non-neutral - mutable bool used_in_GEType_; - // all_neutral_DES_ is true if and only if the DES for all traits is "f" 0.0. Mutations of this type // could still be non-neutral (because they were changed, or created at a time when the DES was not neutral), // and callbacks could still change mutation effects. What this flag does tell you is that if a new mutation diff --git a/core/population.cpp b/core/population.cpp index c906a8c3..54d6f589 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -877,12 +877,12 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind for (slim_popsize_t female_index = 0; female_index < p_source_subpop->parent_first_male_index_; female_index++) local_weights_data[female_index] = 0; for (slim_popsize_t male_index = p_source_subpop->parent_first_male_index_; male_index < weights_length; male_index++) - local_weights_data[male_index] = individuals_data[male_index]->cached_fitness_UNSAFE_; + local_weights_data[male_index] = (double)individuals_data[male_index]->cached_fitness_UNSAFE_; } else { for (slim_popsize_t individual_index = 0; individual_index < weights_length; individual_index++) - local_weights_data[individual_index] = individuals_data[individual_index]->cached_fitness_UNSAFE_; + local_weights_data[individual_index] = (double)individuals_data[individual_index]->cached_fitness_UNSAFE_; } } @@ -3232,8 +3232,6 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h if (new_mutation != -1) mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // NoteNonNeutralMutation() has already been called by DrawNewMutationExtended() if necessary - // see further comments below, in the non-nucleotide case; they apply here as well } } @@ -3246,8 +3244,6 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // NoteNonNeutralMutation() has already been called by DrawNewMutation() if necessary - // we can't handle the stacking policy here, since we don't yet know what the context of the new mutation will be; we do it below // we add the new mutation to the registry below, if the stacking policy says the mutation can actually be added } @@ -3877,8 +3873,6 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha if (new_mutation != -1) mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // NoteNonNeutralMutation() has already been called by DrawNewMutationExtended() if necessary - // see further comments below, in the non-nucleotide case; they apply here as well } } @@ -3891,8 +3885,6 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // NoteNonNeutralMutation() has already been called by DrawNewMutation() if necessary - // we can't handle the stacking policy here, since we don't yet know what the context of the new mutation will be; we do it below // we add the new mutation to the registry below, if the stacking policy says the mutation can actually be added } @@ -4296,8 +4288,6 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil mutations_to_add.emplace_back(new_mutation); // positions are already sorted - // NoteNonNeutralMutation() has already been called by DrawNewMutation() if necessary - // we can't handle the stacking policy here, since we don't yet know what the context of the new mutation will be; we do it below // we add the new mutation to the registry below, if the stacking policy says the mutation can actually be added } diff --git a/core/species.cpp b/core/species.cpp index 895c5c17..6c063329 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -217,18 +217,100 @@ Subpopulation *Species::SubpopulationWithName(const std::string &p_subpop_name) return nullptr; } -void Species::NoteNonNeutralMutation(Mutation *p_mut) +void Species::AutogenerationConfigurationChanged(void) { - // This should be called whenever a non-neutral mutation is added to the simulation, or an existing - // mutation is made non-neutral, or an existing non-neutral mutation changes mutation type: anything - // that would affect the optimization flags we keep. The goal here is to centralize this logic. -#if DEBUG - if (p_mut->is_neutral_) - EIDOS_TERMINATION << "ERROR (Species::NoteNonNeutralMutation): (internal error) NoteNonNeutralMutation() called for neutral mutation." << EidosTerminate(); -#endif + // This is called when a GenomicElementType / MutationType is changed, including at the end of initialize() + // callbacks. It runs through the autogeneration configuration for the species and sets optimization flags. + int registry_count = 0; + population_.MutationRegistry(®istry_count); - species_all_neutral_mutations_ = false; - p_mut->mutation_type_ptr_->muttype_all_neutral_mutations_ = false; + // First we loop through all the mutation types (whether in use by a GEType or not) and validate them + type_s_DESs_present_ = false; + + for (auto mut_type_iter : mutation_types_) + { + MutationType *mutation_type_ptr = mut_type_iter.second; + + // recalculate whether the mutation type's effect distributions are all neutral + mutation_type_ptr->all_neutral_DES_ = true; + + for (EffectDistributionInfo &DES_info : mutation_type_ptr->effect_distributions_) + { + if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) + mutation_type_ptr->all_neutral_DES_ = false; + + if (DES_info.DES_type_ == DESType::kScript) + type_s_DESs_present_ = true; + } + + // recalculate whether the mutation type has any non-neutral mutations associated with it + // normally this flag is sticky, because non-neutral mutations could exist even if all_neutral_DES_ + // is true, but if there are no mutations segregating we can reset this flag and recover neutrality + if (registry_count == 0) + mutation_type_ptr->muttype_all_neutral_mutations_ = true; + + if (!mutation_type_ptr->all_neutral_DES_) + mutation_type_ptr->muttype_all_neutral_mutations_ = false; + } + + // Then we loop through the GETypes and determine whether the species has any non-neutral mutations; it is + // assumed that if a GEType uses a non-neutral mutation type the species is non-neutral. Normally this + // flag is sticky, but if there are no segregating mutations we can reset it and recover neutrality. + if (registry_count == 0) + species_all_neutral_mutations_ = true; + + for (auto ge_type_iter : genomic_element_types_) + { + GenomicElementType *ge_type_ptr = ge_type_iter.second; + + for (MutationType *mutation_type_ptr : ge_type_ptr->mutation_type_ptrs_) + if (!mutation_type_ptr->all_neutral_DES_) + species_all_neutral_mutations_ = false; + } + + // These flags cause a refresh of the user interface in SLiMgui. We don't know for sure here that these + // flags need to be set, but assuming it simplifies our design and the impact should be minimal. + community_.mutation_types_changed_ = true; + community_.genomic_element_types_changed_ = true; +} + +void Species::CheckOptimizationFlags(void) +{ + // This is called to check that the various optimization flags set by AutogenerationConfigurationChanged() + // are in the correct state; incorrect results will be produced if the optimization flags are wrong! + + for (auto mut_type_iter : mutation_types_) + { + MutationType *mutation_type_ptr = mut_type_iter.second; + + for (EffectDistributionInfo &DES_info : mutation_type_ptr->effect_distributions_) + { + if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) + if (mutation_type_ptr->all_neutral_DES_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) all_neutral_DES_ is incorrect." << EidosTerminate(); + + if (DES_info.DES_type_ == DESType::kScript) + if (type_s_DESs_present_ != true) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) type_s_DESs_present_ is incorrect." << EidosTerminate(); + } + + // recalculate whether the mutation type has any non-neutral mutations associated with it + // normally this flag is sticky, because non-neutral mutations could exist even if all_neutral_DES_ + // is true, but if there are no mutations segregating we can reset this flag and recover neutrality + if (!mutation_type_ptr->all_neutral_DES_) + if (mutation_type_ptr->muttype_all_neutral_mutations_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) muttype_all_neutral_mutations_ is incorrect." << EidosTerminate(); + } + + for (auto ge_type_iter : genomic_element_types_) + { + GenomicElementType *ge_type_ptr = ge_type_iter.second; + + for (MutationType *mutation_type_ptr : ge_type_ptr->mutation_type_ptrs_) + if (!mutation_type_ptr->all_neutral_DES_) + if (species_all_neutral_mutations_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) species_all_neutral_mutations_ is incorrect." << EidosTerminate(); + } } // Chromosome management @@ -1342,10 +1424,6 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos if (population_.keeping_muttype_registries_) EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromTextFile): (internal error) separate muttype registries set up during pop load." << EidosTerminate(); #endif - - // all mutations seen here will be added to the simulation somewhere, so tell the species about it - if (!new_mut->is_neutral_) - NoteNonNeutralMutation(new_mut); } population_.InvalidateMutationReferencesCache(); @@ -2106,10 +2184,6 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid if (population_.keeping_muttype_registries_) EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): (internal error) separate muttype registries set up during pop load." << EidosTerminate(); #endif - - // all mutations seen here will be added to the simulation somewhere, so tell the species about it - if (!new_mut->is_neutral_) - NoteNonNeutralMutation(new_mut); } population_.InvalidateMutationReferencesCache(); @@ -2439,48 +2513,6 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid population_.InvalidateMutationReferencesCache(); // force a retally population_.TallyMutationReferencesAcrossPopulation(/* p_clock_for_mutrun_experiments */ false); - if (file_version <= 2) - { - // Now that we have the info on everybody, update fitnesses so that we're ready to run the next cycle - // used to be generation + 1; removing that 18 Feb 2016 BCH - - nonneutral_change_counter_++; // trigger unconditional nonneutral mutation caching inside UpdateFitness() - last_nonneutral_regime_ = 3; // this means "unpredictable callbacks", will trigger a recache next cycle - - for (auto muttype_iter : mutation_types_) - (muttype_iter.second)->subject_to_mutationEffect_callback_ = true; // we're not doing RecalculateFitness()'s work, so play it safe - - SLiMEidosBlockType old_executing_block_type = community_.executing_block_type_; - community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMutationEffectCallback; // used for both mutationEffect() and fitnessEffect() for simplicity - community_.executing_species_ = this; - - // we need to recalculate phenotypes for traits that have a direct effect on fitness - std::vector p_direct_effect_trait_indices; - const std::vector &traits = Traits(); - - for (slim_trait_index_t trait_index = 0; trait_index < TraitCount(); ++trait_index) - if (traits[trait_index]->HasDirectFitnessEffect()) - p_direct_effect_trait_indices.push_back(trait_index); - - for (std::pair &subpop_pair : population_.subpops_) - { - slim_objectid_t subpop_id = subpop_pair.first; - Subpopulation *subpop = subpop_pair.second; - std::vector mutationEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, subpop_id, -1, -1); - std::vector fitnessEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, subpop_id, -1, -1); - - subpop->UpdateFitness(mutationEffect_callbacks, fitnessEffect_callbacks, p_direct_effect_trait_indices, /* p_force_trait_recalculation */ true); - } - - community_.executing_block_type_ = old_executing_block_type; - community_.executing_species_ = nullptr; - -#ifdef SLIMGUI - // Let SLiMgui survey the population for mean fitness and such, if it is our target - population_.SurveyPopulation(); -#endif - } - return file_tick; } #else @@ -2744,6 +2776,11 @@ void Species::RunInitializeCallbacks(void) EIDOS_TERMINATION << "ERROR (Species::RunInitializeCallbacks): The chromosome length (" << chromosome->last_position_ + 1 << " base" << (chromosome->last_position_ + 1 != 1 ? "s" : "") << ") does not match the ancestral sequence length (" << chromosome->ancestral_seq_buffer_->size() << " base" << (chromosome->ancestral_seq_buffer_->size() != 1 ? "s" : "") << ")." << EidosTerminate(); } + // notify the species that the mutation autogeneration configuration has changed; rather than + // calling this for each change during initialize(), we call it once here at the end; note + // that this design means that the flags managed by this method will be incorrect until here + AutogenerationConfigurationChanged(); + // always start at cycle 1, regardless of what the starting tick value might be SetCycle(1); @@ -9959,9 +9996,10 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapis_neutral_) - // NoteNonNeutralMutation(new_mut); + // FIXME MULTITRAIT: non-neutral substitutions don't make the simulation non-neutral, so no + // NoteChangedMutation() type of call is needed, I think? But what if substitution effects + // are merged into baseline offsets? Might require more thought here. See also where + // new substitutions are created by readFromPopulationFile()... } else { @@ -9980,10 +10018,6 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapis_neutral_) - NoteNonNeutralMutation(new_mut); } } } diff --git a/core/species.h b/core/species.h index befe4002..efe36f66 100644 --- a/core/species.h +++ b/core/species.h @@ -388,6 +388,8 @@ class Species : public EidosDictionaryUnretained std::string description_; // the `description` property; the empty string by default slim_objectid_t species_id_; // the identifier for the species, which its index into the Community's species vector + // OPTIMIZATION FLAGS + bool has_recalculated_fitness_ = false; // set to true when recalculateFitness() is called, so we know fitness values are valid // optimization of the pure neutral case; this is set to false if (a) a non-neutral mutation is added by the user, (b) a genomic element type is configured to use a @@ -430,7 +432,45 @@ class Species : public EidosDictionaryUnretained Species(Community &p_community, slim_objectid_t p_species_id, const std::string &p_name); // construct a Species from a community / id / name ~Species(void); // destructor - void NoteNonNeutralMutation(Mutation *p_mut); // call whenever a non-neutral mutation needs to be noted to update optimization flags + // Noting changes that affect optimization flags + void AutogenerationConfigurationChanged(void); // called when a GenomicElementType / MutationType is changed + void CheckOptimizationFlags(void); // essentially checks what AutogenerationConfigurationChanged() does + + inline __attribute__((always_inline)) void NoteAutogeneratedMutation(Mutation *p_mut) + { + // NoteAutogeneratedMutation() should be called whenever a new mutation is auto-generated by SLiM. + // This is a different path from NoteChangedMutation() because the characteristics of these mutations + // is known, and has already been factored in to SLiM optimization settings. + if (!p_mut->is_neutral_for_all_traits_) + { + p_mut->mutation_type_ptr_->species_.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags + } + else + { + // In this case we do not need to invalidate non-neutral caches; the mutation is a new one, + // and is neutral for all traits, so there is no implication for our caching scheme + } + } + + inline __attribute__((always_inline)) void NoteChangedMutation(Mutation *p_mut) + { + // NoteChangedMutation() should be called whenever a mutation is added/changed in a way that is not + // driven by SLiM's auto-generation configuration (i.e., GenomicElementType and MutationType). The + // characteristics of this kind of mutation are not known, so they have to be examined closely. + if (!p_mut->is_neutral_for_all_traits_) + { + species_all_neutral_mutations_ = false; + p_mut->mutation_type_ptr_->muttype_all_neutral_mutations_ = false; + + p_mut->mutation_type_ptr_->species_.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags + } + else + { + // This needs to be done in the neutral case too; for example, a non-neutral mutation might + // have been changed into a neutral mutation, which invalidates our non-neutral caches + p_mut->mutation_type_ptr_->species_.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags + } + } // Chromosome configuration and access inline __attribute__((always_inline)) const std::vector &Chromosomes(void) { return chromosomes_; } diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 75ae0716..b44bccd3 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -532,15 +532,6 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const { mutation_types.emplace_back(mutation_type_ptr); mutation_fractions.emplace_back(proportion); - - // let the mutation type know that it is being used - mutation_type_ptr->used_in_GEType_ = true; - - // check whether the mutation type is non-neutral; if so, the species is now considered non-neutral - // (because we expect that a non-neutral mutation will be generated by this genomic element type) - // see also ExecuteMethod_setMutationFractions() and ExecuteMethod_setEffectDistributionForTrait() - if (!mutation_type_ptr->all_neutral_DES_) - species_all_neutral_mutations_ = false; } } } @@ -557,7 +548,6 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const new_genomic_element_type->SetNucleotideMutationMatrix(EidosValue_Float_SP((EidosValue_Float *)(mutationMatrix_value))); genomic_element_types_.emplace(map_identifier, new_genomic_element_type); - community_.genomic_element_types_changed_ = true; // define a new Eidos variable to refer to the new genomic element type EidosSymbolTableEntry &symbol_entry = new_genomic_element_type->SymbolTableEntry(); @@ -676,11 +666,6 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: #endif mutation_types_.emplace(map_identifier, new_mutation_type); - community_.mutation_types_changed_ = true; - - // keep track of whether we have ever seen a type 's' (scripted) DES; if so, we switch to a slower case when evolving - if (DES_type == DESType::kScript) - type_s_DESs_present_ = true; // define a new Eidos variable to refer to the new mutation type EidosSymbolTableEntry &symbol_entry = new_mutation_type->SymbolTableEntry(); @@ -4651,8 +4636,6 @@ EidosValue_SP Species::ExecuteMethod_treeSeqOutput(EidosGlobalStringID p_method_ if (!recording_tree_) EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_treeSeqOutput): treeSeqOutput() may only be called when tree recording is enabled." << EidosTerminate(); - SLiMCycleStage cycle_stage = community_.CycleStage(); - // TIMING RESTRICTION community_.EnforceTimingRestriction_EventBlockOnly("Species::ExecuteMethod_treeSeqOutput", "treeSeqOutput()", ""); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 2a3c2531..8d5842cb 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -7804,8 +7804,6 @@ EidosValue_SP Subpopulation::ExecuteMethod_deviatePositions(EidosGlobalStringID // NOTE: most of the code of this method is shared with pointDeviated() - SLiMCycleStage cycle_stage = community_.CycleStage(); - // TIMING RESTRICTION community_.EnforceTimingRestriction_EventBlockOnly("Subpopulation::ExecuteMethod_deviatePositions", "deviatePositions()", ""); @@ -8225,8 +8223,6 @@ EidosValue_SP Subpopulation::ExecuteMethod_deviatePositionsWithMap(EidosGlobalSt // NOTE: most of the code of this method is shared with pointDeviated(), and even more, with deviatePositions() - SLiMCycleStage cycle_stage = community_.CycleStage(); - // TIMING RESTRICTION community_.EnforceTimingRestriction_EventBlockOnly("Subpopulation::ExecuteMethod_deviatePositionsWithMap", "deviatePositionsWithMap()", ""); diff --git a/core/substitution.cpp b/core/substitution.cpp index fc68b747..f99b7655 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -37,7 +37,7 @@ #pragma mark - Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : -EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), is_neutral_(p_mutation.is_neutral_), is_independent_dominance_(p_mutation.is_independent_dominance_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) +EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), is_neutral_for_all_traits_(p_mutation.is_neutral_for_all_traits_), is_neutral_for_direct_fitness_traits_(p_mutation.is_neutral_for_direct_fitness_traits_), is_independent_dominance_(p_mutation.is_independent_dominance_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) { AddKeysAndValuesFrom(&p_mutation); @@ -73,9 +73,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ trait_info_ = (SubstitutionTraitInfo *)malloc(trait_count * sizeof(SubstitutionTraitInfo)); - // We need to infer the values of the is_neutral_ and is_independent_dominance_ flags + // We need to infer the values of the is_neutral_for_all_traits_ and is_independent_dominance_ flags // FIXME MULTITRAIT: needs to be fixed when the below issues are fixed - is_neutral_ = (p_selection_coeff == (slim_effect_t)0.0); + is_neutral_for_all_traits_ = (p_selection_coeff == (slim_effect_t)0.0); + is_neutral_for_direct_fitness_traits_ = is_neutral_for_all_traits_; // FIXME MULTITRAIT is_independent_dominance_ = std::isnan(p_dominance_coeff); trait_info_[0].effect_size_ = p_selection_coeff; @@ -102,11 +103,14 @@ void Substitution::SelfConsistencyCheck(const std::string &p_message_end) EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): (internal error) trait_info_ is nullptr" << p_message_end << "." << EidosTerminate(); Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); slim_trait_index_t trait_count = species.TraitCount(); bool all_neutral_effects = true; + bool all_neutral_direct_effects = true; for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { + Trait *trait = traits[trait_index]; SubstitutionTraitInfo &traitInfoRec = trait_info_[trait_index]; if (!std::isfinite(traitInfoRec.effect_size_)) @@ -121,11 +125,21 @@ void Substitution::SelfConsistencyCheck(const std::string &p_message_end) EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution independent dominance state is inconsistent" << p_message_end << "." << EidosTerminate(); if (traitInfoRec.effect_size_ != (slim_effect_t)0.0) + { all_neutral_effects = false; + + if (trait->HasDirectFitnessEffect()) + all_neutral_direct_effects = false; + } } - if ((is_neutral_ && !all_neutral_effects) || (!is_neutral_ && all_neutral_effects)) + if ((is_neutral_for_all_traits_ && !all_neutral_effects) || + (!is_neutral_for_all_traits_ && all_neutral_effects)) EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution neutrality state is inconsistent" << p_message_end << "." << EidosTerminate(); + + if ((is_neutral_for_direct_fitness_traits_ && !all_neutral_direct_effects) || + (!is_neutral_for_direct_fitness_traits_ && all_neutral_direct_effects)) + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution direct-effect neutrality state is inconsistent" << p_message_end << "." << EidosTerminate(); } slim_effect_t Substitution::RealizedDominanceForTrait(Trait *p_trait) @@ -299,7 +313,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) case gID_isIndependentDominance: // ACCELERATED return (is_independent_dominance_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_isNeutral: // ACCELERATED - return (is_neutral_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + return (is_neutral_for_all_traits_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_mutationType: // ACCELERATED return mutation_type_ptr_->SymbolTableEntry().second; case gID_position: // ACCELERATED @@ -517,7 +531,7 @@ EidosValue *Substitution::GetProperty_Accelerated_isNeutral(EidosGlobalStringID { Substitution *value = (Substitution *)(p_values[value_index]); - logical_result->set_logical_no_check(value->is_neutral_, value_index); + logical_result->set_logical_no_check(value->is_neutral_for_all_traits_, value_index); } return logical_result; diff --git a/core/substitution.h b/core/substitution.h index 8b9d6288..db77f5ba 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -66,19 +66,20 @@ class Substitution : public EidosDictionaryRetained public: - MutationType *mutation_type_ptr_; // mutation type identifier - slim_position_t position_; // position - slim_objectid_t subpop_index_; // subpopulation in which mutation arose - slim_tick_t origin_tick_; // tick in which mutation arose - slim_tick_t fixation_tick_; // tick in which mutation fixed - slim_chromosome_index_t chromosome_index_; // the (uint8_t) index of this mutation's chromosome + MutationType *mutation_type_ptr_; // mutation type identifier + slim_position_t position_; // position + slim_objectid_t subpop_index_; // subpopulation in which mutation arose + slim_tick_t origin_tick_; // tick in which mutation arose + slim_tick_t fixation_tick_; // tick in which mutation fixed + slim_chromosome_index_t chromosome_index_; // the (uint8_t) index of this mutation's chromosome - unsigned int is_neutral_ : 1; // all effects are 0.0; see mutation.h - unsigned int is_independent_dominance_ : 1; // configured for "independent dominance"; see mutation.h + unsigned int is_neutral_for_all_traits_ : 1; // all effects are 0.0; see mutation.h + unsigned int is_neutral_for_direct_fitness_traits_ : 1; // all effects for direct fitness traits are 0.0 + unsigned int is_independent_dominance_ : 1; // configured for "independent dominance"; see mutation.h - int8_t nucleotide_; // the nucleotide being kept: A=0, C=1, G=2, T=3. -1 is used to indicate non-nucleotide-based. - const slim_mutationid_t mutation_id_; // a unique id for each mutation, used to track mutations - slim_usertag_t tag_value_; // a user-defined tag value + int8_t nucleotide_; // A=0, C=1, G=2, T=3. -1 means non-nucleotide-based. + const slim_mutationid_t mutation_id_; // a unique id for each mutation, to track mutations + slim_usertag_t tag_value_; // a user-defined tag value // Per-trait information SubstitutionTraitInfo *trait_info_; // OWNED: a malloced block of per-trait information From 3f8e0135ab93a1b6d9deb9d960177d4f12378669 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 7 Jan 2026 09:44:03 -0600 Subject: [PATCH 070/107] more optimization flag and independent dominance work --- QtSLiM/help/SLiMHelpClasses.html | 17 ++- QtSLiM/help/SLiMHelpFunctions.html | 2 +- SLiMgui/SLiMHelpClasses.rtf | 121 +++++++++------ SLiMgui/SLiMHelpFunctions.rtf | 22 +-- VERSIONS | 17 ++- core/mutation.cpp | 231 +++++++++++++++++++---------- core/mutation.h | 36 +++-- core/mutation_type.cpp | 5 - core/slim_globals.cpp | 2 +- core/slim_globals.h | 4 +- core/slim_test_genetics.cpp | 16 +- core/species.cpp | 142 +++++++++++++++++- core/species.h | 22 +-- core/substitution.cpp | 124 ++++++++++++---- core/substitution.h | 10 +- core/trait.h | 10 ++ 16 files changed, 543 insertions(+), 238 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 1d3925ec..b77f6586 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -694,7 +694,7 @@

The Chromosome object with which the mutation is associated.

dominance => (float)

The dominance coefficient(s) of the mutation, taken from the default dominance coefficient(s) of its MutationType.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightDominance to access the dominance for that trait.  The dominance coefficient(s) of a mutation can be changed with the setDominanceForTrait() method.

-

If the target mutation has been configured to exhibit independent dominance by setting its dominance values to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this property does not provide that value of NAN; instead, it provides the dominance values that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance values.  These realized dominance values depend upon the mutation’s corresponding effects, and may change if those effects change.  The class Trait documentation provides further discussion of independent dominance.

+

If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this property does not provide that value of NAN; instead, it provides the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance value (see isIndependentDominanceForTrait() for the way to determine whether independent dominance is configured for a trait).  This realized dominance value depends upon the mutation’s corresponding effect, and may change if the effect changes.  The class Trait documentation provides further discussion of independent dominance.

Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s dominance coefficient to some number x, mut.dominance==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

effect => (float)

The effect size(s) of the mutation, drawn from the distribution of effect sizes of its MutationType.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightEffect to access the effect for that trait.  The effect size of a mutation can be changed with the setEffectForTrait() method.

@@ -706,8 +706,6 @@

The identifier for this mutation.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run.  These identifiers are not re-used during a run, except that if a population file is loaded from disk, the loaded mutations will receive their original identifier values as saved in the population file.

isFixed => (logical$)

T if the mutation has fixed (in the SLiM sense of having been converted to a Substitution object), F otherwise.  Since fixed/substituted mutations are removed from the simulation, you will only see this flag be T if you have held onto a mutation beyond its usual lifetime.

-

isIndependentDominance => (logical$)

-

T if the mutation is considered to exhibit independent dominance, F otherwise.  For a mutation to be considered to exhibit independent dominance, it must have received its dominance from a MutationType configured for independent dominance (with a default dominance of NAN), or had its dominance configured as NAN for all traits with setDominanceForTrait(); simply having the appropriate dominance value is not sufficient for this determination.  Its mutation type and effect are irrelevant to this determination.  A mutation can be T for both isIndependentDominance and isNeutral; in this case, the mutation is configured to exhibit independent dominance, but happens to currently have effects of 0.0 for all traits.  See the class Trait documentation in for further discussion of independent dominance.

isNeutral => (logical$)

T if the mutation is neutral, F otherwise.  For a mutation to be considered neutral, it must have an effect of exactly 0.0 for all traits; its mutation type and dominance are irrelevant to this determination.

isSegregating => (logical$)

@@ -730,15 +728,17 @@

5.10.2  Mutation methods

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

-

If the target mutation has been configured to exhibit independent dominance by setting its dominance values to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this method does not return that value of NAN; instead, it returns the dominance values that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance values.  These realized dominance values depend upon the mutation’s corresponding effects, and may change if those effects change.  The class Trait documentation provides further discussion of independent dominance.

+

If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this method does not return that value of NAN; instead, it returns the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance value (see isIndependentDominanceForTrait() for the way to determine whether independent dominance is configured for a trait).  This realized dominance value depends upon the mutation’s corresponding effect, and may change if that effect changes.  The class Trait documentation provides further discussion of independent dominance.

– (float)effectForTrait([Niso<Trait> trait = NULL])

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

+

Returns whether the mutation is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A mutation is configured for independent dominance for a trait if it inherited a default dominance of NAN for the trait from its mutation type, or if its dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation in section 26.19 for discussion of this feature.  Independent dominance flags for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+ (void)setDominanceForTrait([Niso<Trait> trait = NULL], [Nif dominance = NULL])

Sets the target mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.)  In the second pattern, dominance is a singleton value; this sets the given dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the dominance for each of the specified traits in one mutation before moving to the next mutation.

-

A dominance value of NAN configures the mutation to use independent dominance; see the class Trait documentation for discussion of this feature, which can also be set at the MutationType level with initializeMutationType() or setDefaultDominanceForTrait().  A given mutation must be configured to exhibit independent dominance for all traits or for none; a mixed configuration is not allowed.

+

A dominance value of NAN for a given trait configures the mutation to use independent dominance for that trait; see the class Trait documentation in section 26.19 for discussion of this feature, which can also be set at the MutationType level with initializeMutationType() or setDefaultDominanceForTrait().  A given mutation may be configured to exhibit independent dominance for some traits and not for others; a mixed configuration is allowed.

+ (void)setEffectForTrait([Niso<Trait> trait = NULL], [Nif effect = NULL])

Sets the mutation’s effect(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter effect must follow one of four patterns.  In the first pattern, effect is NULL; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation.  (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.)  In the second pattern, effect is a singleton value; this sets the given effect for each of the specified traits in each target mutation.  In the third pattern, effect is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation.  In the fourth pattern, effect is of length equal to the number of specified traits times the number of target mutations; this uses effect to provide a different effect value for each trait in each mutation, using consecutive values from effect to set the effect for each of the specified traits in one mutation before moving to the next mutation.

@@ -779,6 +779,7 @@

5.11.2  MutationType methods

– (float)defaultDominanceForTrait([Niso<Trait> trait = NULL])

Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their dominance property, but that can be changed later with the Mutation method setDominanceForTrait().

+

If a default dominance coefficient of NAN has been set for a given trait, to configure a default of “independent dominance” for that trait, NAN will be returned by this method for that trait; a realized dominance value cannot be returned since the corresponding effect size is not necessarily known.  See the Mutation method dominanceForTrait() regarding obtaining the realized dominance coefficient used in a particular mutation, given that mutation’s effect for the trait.

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

– (float)defaultHemizygousDominanceForTrait([Niso<Trait> trait = NULL])

@@ -793,7 +794,7 @@

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

– (void)setDefaultDominanceForTrait(Niso<Trait> trait, float dominance)

Set the default dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of defaultDominance is used for each corresponding trait).

-

As for initializeMutationType(), a dominance value of NAN configures the mutation type to use “independent dominance” for new mutations of that type; see the class Trait documentation for discussion of independent dominance.  If the mutation type is configured to use independent dominance for one trait, it must use it for all traits; this is because the same restriction applies to mutations themselves.

+

As for initializeMutationType(), a dominance value of NAN for a given trait configures the mutation type to use “independent dominance” for that trait in new mutations of that type; see the class Trait documentation for discussion of independent dominance.  A mutation type may be configured to use independent dominance for some traits and not for others; a mixed configuration is allowed.

– (void)setDefaultHemizygousDominanceForTrait(Niso<Trait> trait, float dominance)

Set the default hemizygous dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default hemizygous dominance for all specified traits), or must match the number of specified traits (in which case one element of dominance is used for each corresponding trait).

– (void)setEffectDistributionForTrait(Niso<Trait> trait, string$ distributionType, ...)

@@ -1380,8 +1381,6 @@

The hemizygous dominance coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the hemizygousDominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightHemizygousDominance to access the dominance for that trait.

id => (integer$)

The identifier for this substitution.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run, and that identifier is carried over to the Substitution object when the mutation fixes.

-

isIndependentDominance => (logical$)

-

T if the substitution is considered to exhibit independent dominance, F otherwise.  The value of this property is carried over from the original mutation; see the same property on Mutation for further details.

isNeutral => (logical$)

T if the substitution is neutral, F otherwise.  The value of this property is carried over from the original mutation; see the same property on Mutation for further details.

fixationTick => (integer$)

@@ -1407,6 +1406,8 @@

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

Returns the substitution’s hemizygous dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

+

Returns whether the substitution is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A substitution is configured for independent dominance for a trait if the original mutation inherited a default dominance of NAN for the trait from its mutation type, or if the original mutation’s dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation in section 26.19 for discussion of this feature.  Independent dominance flags for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

5.19  Class Trait

5.19.1  Trait properties

baselineOffset <–> (float$)

diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 5be47898..3de050cd 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -105,7 +105,7 @@

This function is written in Eidos, and its source code can be viewed with functionSource(), so you can copy and modify its code if you need to modify its functionality.

(object<MutationType>$)initializeMutationType(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

Add a mutation type at initialization time.  The id must not already be used for any mutation type in the simulation.  The id parameter may be either an integer giving the ID of the new mutation type, or a string giving the name of the new mutation type (such as "m5" to specify an ID of 5).  The global symbol for the new mutation type, such as m5, is immediately available; the return value also provides the new object.

-

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but the setDefaultHemizygousDominanceForTrait() method can configure it subsequently if desired.  A dominanceCoeff value of NAN configures the mutation type to use “independent dominance” for new mutations of that type; see the class Trait documentation for discussion of independent dominance.

+

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  A dominanceCoeff value of NAN configures the mutation type to use “independent dominance” for new mutations of that type, for all traits; see the class Trait documentation for discussion of independent dominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but the setDefaultHemizygousDominanceForTrait() method can configure it subsequently if desired.

The distributionType and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits.  The DES for the mutation type for a specific trait can subsequently be separately configured with the setEffectDistributionForTrait() method if desired.  The distributionType parameter may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for an exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  If distributionType is NULL (the default), a fixed effect of 0.0 is used, representing a neutral DES; this is equivalent to type "f" except that the value 0.0 is assumed and must not be supplied.  See the MutationType class documentation for discussion of the various DESs and their uses.

Note that by default in WF models, all mutations of a given mutation type will be converted into Substitution objects when they reach fixation, for efficiency reasons.  If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the convertToSubstitution property of MutationType to F.  In contrast, by default in nonWF models mutations will not be converted into Substitution objects when they reach fixation; convertToSubstitution is F by default in nonWF models.  To enable conversion in nonWF models for neutral mutation types with no indirect fitness effects, you should therefore set convertToSubstitution to T.

(object<MutationType>$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index dad35f84..883c3440 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -6166,7 +6166,7 @@ You can get the \f4\fs20 to access the dominance for that trait. The dominance coefficient(s) of a mutation can be changed with the \f3\fs18 setDominanceForTrait() \f4\fs20 method.\ -If the target mutation has been configured to exhibit independent dominance by setting its dominance values to +If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to \f3\fs18 NAN \f4\fs20 , as discussed in \f3\fs18 initializeMutationType() @@ -6174,7 +6174,9 @@ If the target mutation has been configured to exhibit independent dominance by s \f3\fs18 setDominanceForTrait() \f4\fs20 , this property does not provide that value of \f3\fs18 NAN -\f4\fs20 ; instead, it provides the dominance values that will actually be used by SLiM to implement independent dominance, referred to as the \'93realized\'94 dominance values. These realized dominance values depend upon the mutation\'92s corresponding effects, and may change if those effects change. The class +\f4\fs20 ; instead, it provides the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the \'93realized\'94 dominance value (see +\f3\fs18 isIndependentDominanceForTrait() +\f4\fs20 for the way to determine whether independent dominance is configured for a trait). This realized dominance value depends upon the mutation\'92s corresponding effect, and may change if the effect changes. The class \f3\fs18 Trait \f4\fs20 documentation provides further discussion of independent dominance.\ Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation @@ -6261,32 +6263,6 @@ Note that dominance coefficients in SLiM have a quirk: they are stored internall \f4\fs20 if you have held onto a mutation beyond its usual lifetime.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 isIndependentDominance => (logical$)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 T -\f4\fs20 if the mutation is considered to exhibit independent dominance, -\f3\fs18 F -\f4\fs20 otherwise. For a mutation to be considered to exhibit independent dominance, it must have received its dominance from a -\f3\fs18 MutationType -\f4\fs20 configured for independent dominance (with a default dominance of -\f3\fs18 NAN -\f4\fs20 ), or had its dominance configured as -\f3\fs18 NAN -\f4\fs20 for all traits with -\f3\fs18 setDominanceForTrait() -\f4\fs20 ; simply having the appropriate dominance value is not sufficient for this determination. Its mutation type and effect are irrelevant to this determination. A mutation can be -\f3\fs18 T -\f4\fs20 for both -\f3\fs18 isIndependentDominance -\f4\fs20 and -\f3\fs18 isNeutral -\f4\fs20 ; in this case, the mutation is configured to exhibit independent dominance, but happens to currently have effects of -\f3\fs18 0.0 -\f4\fs20 for all traits. See the class -\f3\fs18 Trait -\f4\fs20 documentation in for further discussion of independent dominance.\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - \f3\fs18 \cf2 isNeutral => (logical$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf2 T @@ -6424,7 +6400,7 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait \f4\fs20 .\ -If the target mutation has been configured to exhibit independent dominance by setting its dominance values to +If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to \f3\fs18 NAN \f4\fs20 , as discussed in \f3\fs18 initializeMutationType() @@ -6432,7 +6408,9 @@ If the target mutation has been configured to exhibit independent dominance by s \f3\fs18 setDominanceForTrait() \f4\fs20 , this method does not return that value of \f3\fs18 NAN -\f4\fs20 ; instead, it returns the dominance values that will actually be used by SLiM to implement independent dominance, referred to as the \'93realized\'94 dominance values. These realized dominance values depend upon the mutation\'92s corresponding effects, and may change if those effects change. The class +\f4\fs20 ; instead, it returns the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the \'93realized\'94 dominance value (see +\f3\fs18 isIndependentDominanceForTrait() +\f4\fs20 for the way to determine whether independent dominance is configured for a trait). This realized dominance value depends upon the mutation\'92s corresponding effect, and may change if that effect changes. The class \f3\fs18 Trait \f4\fs20 documentation provides further discussion of independent dominance.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -6480,6 +6458,32 @@ If the target mutation has been configured to exhibit independent dominance by s \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(logical)isIndependentDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns whether the mutation is configured for independent dominance for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. A mutation is configured for independent dominance for a trait if it inherited a default dominance of +\f3\fs18 NAN +\f4\fs20 for the trait from its mutation type, or if its dominance for the trait was subsequently set to +\f3\fs18 NAN +\f4\fs20 with +\f3\fs18 setDominanceForTrait() +\f4\fs20 ; see the class +\f3\fs18 Trait +\f4\fs20 documentation in section 26.19 for discussion of this feature. Independent dominance flags for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 +\'a0(void)setDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -6513,15 +6517,15 @@ The parameter \f4\fs20 to set the dominance for each of the specified traits in one mutation before moving to the next mutation.\ A dominance value of \f3\fs18 NAN -\f4\fs20 configures the mutation to use independent dominance; see the class +\f4\fs20 for a given trait configures the mutation to use independent dominance for that trait; see the class \f3\fs18 Trait -\f4\fs20 documentation for discussion of this feature, which can also be set at the +\f4\fs20 documentation in section 26.19 for discussion of this feature, which can also be set at the \f3\fs18 MutationType \f4\fs20 level with \f3\fs18 initializeMutationType() \f4\fs20 or \f3\fs18 setDefaultDominanceForTrait() -\f4\fs20 . A given mutation must be configured to exhibit independent dominance for all traits or for none; a mixed configuration is not allowed.\ +\f4\fs20 . A given mutation may be configured to exhibit independent dominance for some traits and not for others; a mixed configuration is allowed.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 +\'a0(void)setEffectForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0effect\'a0=\'a0NULL])\ @@ -6837,6 +6841,15 @@ The species to which the target object belongs.\ \f4\fs20 method \f3\fs18 setDominanceForTrait() \f4\fs20 .\ +If a default dominance coefficient of +\f3\fs18 NAN +\f4\fs20 has been set for a given trait, to configure a default of \'93independent dominance\'94 for that trait, +\f3\fs18 NAN +\f4\fs20 will be returned by this method for that trait; a realized dominance value cannot be returned since the corresponding effect size is not necessarily known. See the +\f3\fs18 Mutation +\f4\fs20 method +\f3\fs18 dominanceForTrait() +\f4\fs20 regarding obtaining the realized dominance coefficient used in a particular mutation, given that mutation\'92s effect for the trait.\ Note that dominance coefficients are not bounded. A dominance coefficient greater than \f3\fs18 1.0 \f4\fs20 may be used to achieve an overdominance effect. By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ @@ -6967,11 +6980,9 @@ As for \f3\fs18 dominance \f4\fs20 value of \f3\fs18 NAN -\f4\fs20 configures the mutation type to use \'93independent dominance\'94 for new mutations of that type; see the class +\f4\fs20 for a given trait configures the mutation type to use \'93independent dominance\'94 for that trait in new mutations of that type; see the class \f3\fs18 Trait -\f4\fs20 documentation for discussion of independent dominance. If the mutation type is configured to use independent dominance for one trait, it must use it for -\f1\i all -\f4\i0 traits; this is because the same restriction applies to mutations themselves.\ +\f4\fs20 documentation for discussion of independent dominance. A mutation type may be configured to use independent dominance for some traits and not for others; a mixed configuration is allowed.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(void)setDefaultHemizygousDominanceForTrait(Niso\'a0trait, float\'a0dominance)\ @@ -14359,16 +14370,6 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 object when the mutation fixes.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 isIndependentDominance => (logical$)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 T -\f4\fs20 if the substitution is considered to exhibit independent dominance, -\f3\fs18 F -\f4\fs20 otherwise. The value of this property is carried over from the original mutation; see the same property on -\f3\fs18 Mutation -\f4\fs20 for further details.\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - \f3\fs18 \cf2 isNeutral => (logical$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf2 T @@ -14534,6 +14535,32 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait \f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(logical)isIndependentDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns whether the substitution is configured for independent dominance for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. A substitution is configured for independent dominance for a trait if the original mutation inherited a default dominance of +\f3\fs18 NAN +\f4\fs20 for the trait from its mutation type, or if the original mutation\'92s dominance for the trait was subsequently set to +\f3\fs18 NAN +\f4\fs20 with +\f3\fs18 setDominanceForTrait() +\f4\fs20 ; see the class +\f3\fs18 Trait +\f4\fs20 documentation in section 26.19 for discussion of this feature. Independent dominance flags for a given target substitution will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 \f0\b\fs22 \cf2 5.19 Class Trait\ diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 9ba9e8a1..78097c1a 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -887,7 +887,8 @@ This function is written in Eidos, and its source code can be viewed with \f2\fs20 to specify an ID of 5). The global symbol for the new mutation type, such as \f1\fs18 m5 \f2\fs20 , is immediately available; the return value also provides the new object.\ -The +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 The \f1\fs18 dominanceCoeff \f2\fs20 parameter supplies the default dominance coefficient for the mutation type, for all traits; \f1\fs18 0.0 @@ -895,20 +896,21 @@ The \f1\fs18 1.0 \f2\fs20 complete dominance, and values greater than \f1\fs18 1.0 -\f2\fs20 , overdominance. The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the +\f2\fs20 , overdominance. A +\f1\fs18 dominanceCoeff +\f2\fs20 value of +\f1\fs18 NAN +\f2\fs20 configures the mutation type to use \'93independent dominance\'94 for new mutations of that type, for all traits; see the class +\f1\fs18 Trait +\f2\fs20 documentation for discussion of independent dominance. The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the \f1\fs18 setDefaultDominanceForTrait() \f2\fs20 method if desired. Note that the mutation type\'92s default hemizygous dominance coefficient is not supplied to this function; it always defaults to \f1\fs18 1.0 \f2\fs20 , but the \f1\fs18 setDefaultHemizygousDominanceForTrait() -\f2\fs20 method can configure it subsequently if desired. A -\f1\fs18 dominanceCoeff -\f2\fs20 value of -\f1\fs18 NAN -\f2\fs20 configures the mutation type to use \'93independent dominance\'94 for new mutations of that type; see the class -\f1\fs18 Trait -\f2\fs20 documentation for discussion of independent dominance.\ -The +\f2\fs20 method can configure it subsequently if desired.\ +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 The \f1\fs18 distributionType \f2\fs20 and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits. The DES for the mutation type for a specific trait can subsequently be separately configured with the \f1\fs18 setEffectDistributionForTrait() diff --git a/VERSIONS b/VERSIONS index fe0ce406..e8731671 100644 --- a/VERSIONS +++ b/VERSIONS @@ -112,14 +112,15 @@ multitrait branch: extend initializeMutationType() and initializeMutationTypeNuc() to make the effect distribution optional with [Ns$ distributionType = NULL], where NULL is equivalent to `"f", 0.0` make setOffsetForTrait() clamp values to 0.0 for multiplicative traits; fix doc to discuss clamping of effects extend all methods taking a `Nio trait` parameter to `Niso trait`, allowing traits to also be identified by string name - add the concept of "independent dominance" to SLiM; mutations with independent dominance allow optimization because two heterozygous effects equals one homozygous effect - initializeMutationType() and initializeMutationTypeNuc() now allow NAN to be passed for dominanceCoeff, indicating independent dominance for that mutation type (for all traits) - new-mutation construction now sets the `is_independent_dominance_` flag for mutations from such a mutation type - add isIndependentDominance and isNeutral properties to Mutation and Substitution - MutationType's setDefaultDominanceForTrait() now correctly handles converting mutation types to and from a default of independent dominance - Mutation's setDominanceForTrait() method now correctly handles converting mutations to and from independent dominance - a new RealizedDominance() internal (C++) method calculates the correct dominance value to use when independent dominance is configured for a mutation - Mutation and Substitution's dominanceForTrait() methods now use RealizedDominance(), and thus never report NAN as a dominance value (use isIndependentDominance to check for that) + add the concept of "independent dominance" to SLiM; mutations with independent dominance for a trait allow optimization because two heterozygous effects equals one homozygous effect + initializeMutationType() and initializeMutationTypeNuc() now allow NAN to be passed for dominanceCoeff, indicating independent dominance (initially for all traits) + new-mutation construction now sets the `is_independent_dominance_for_any_trait_` and `is_independent_dominance_for_all_traits_` flags for mutations from such a mutation type + add isIndependentDominanceForTrait() method to Mutation and Substitution, to test for independent dominance (since the realized dominance coefficient is provided) + add isNeutral property to Mutation and Substitution (indicating neutrality for all traits) + MutationType's setDefaultDominanceForTrait() now correctly handles the use of NAN to indicate independent dominance + Mutation's setDominanceForTrait() method now correctly handles the use of NAN to indicate independent dominance + a new RealizedDominance() internal (C++) method calculates the correct dominance value to use when independent dominance is configured for a mutation, for a particular trait + Mutation and Substitution's dominanceForTrait() methods now use RealizedDominance(), and thus never report NAN as a dominance value (use isIndependentDominanceForTrait() for that) note that there is no concept of independent dominance for the hemizygous dominance coefficient, since hemizygous mutations are present in only one copy by definition switch fitness calculation over to being based upon the calculated values of traits, rather than directly upon mutations eliminate overhead for setting up fitness buffers in neutral WF models; this should provide a significant speedup for such models, if they don't use a mateChoice() callback diff --git a/core/mutation.cpp b/core/mutation.cpp index a17d1be9..5f2cd875 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -68,8 +68,9 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ is_neutral_for_all_traits_ = true; // will be set to false below as needed is_neutral_for_direct_fitness_traits_ = true; - // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits - is_independent_dominance_ = std::isnan(p_dominance_coeff); + // a dominance coefficient of NAN indicates independent dominance + independent_dominance_for_all_traits_ = true; + independent_dominance_for_any_traits_ = false; for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { @@ -94,6 +95,11 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (trait->HasDirectFitnessEffect()) is_neutral_for_direct_fitness_traits_ = false; + if (std::isnan(dominance)) + independent_dominance_for_any_traits_ = true; + else + independent_dominance_for_all_traits_ = false; + // get the realized dominance to handle the possibility of independent dominance slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -172,8 +178,9 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ is_neutral_for_all_traits_ = true; // will be set to false below as needed is_neutral_for_direct_fitness_traits_ = true; - // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits - is_independent_dominance_ = std::isnan(mutation_type_ptr_->DefaultDominanceForTrait(0)); + // a dominance coefficient of NAN indicates independent dominance + independent_dominance_for_all_traits_ = true; + independent_dominance_for_any_traits_ = false; if (mutation_type_ptr_->all_neutral_DES_) { @@ -229,6 +236,11 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (trait->HasDirectFitnessEffect()) is_neutral_for_direct_fitness_traits_ = false; + if (std::isnan(dominance)) + independent_dominance_for_any_traits_ = true; + else + independent_dominance_for_all_traits_ = false; + // get the realized dominance to handle the possibility of independent dominance slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -304,8 +316,9 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ is_neutral_for_all_traits_ = true; // will be set to false below as needed is_neutral_for_direct_fitness_traits_ = true; - // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits - is_independent_dominance_ = std::isnan(p_dominance_coeff); + // a dominance coefficient of NAN indicates independent dominance + independent_dominance_for_all_traits_ = true; + independent_dominance_for_any_traits_ = false; for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { @@ -330,6 +343,11 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (trait->HasDirectFitnessEffect()) is_neutral_for_direct_fitness_traits_ = false; + if (std::isnan(dominance)) + independent_dominance_for_any_traits_ = true; + else + independent_dominance_for_all_traits_ = false; + // get the realized dominance to handle the possibility of independent dominance slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); @@ -384,8 +402,50 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ gSLiM_next_mutation_id = mutation_id_ + 1; } -void Mutation::SelfConsistencyCheck(const std::string &p_message_end) +void Mutation::EvaluateFlags(void) +{ + // This mirrors Mutation::SelfConsistencyCheck(); essentially it sets up flags as expected by that method. + // When we know the specific nature of a change, there can be some wasted effort here, but changes to the + // state of mutations are not common, and guaranteeing consistency is more important. + Species &species = mutation_type_ptr_->species_; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + const std::vector &traits = species.Traits(); + slim_trait_index_t trait_count = species.TraitCount(); + + // keep flags in local variables until we are ready to set their final values + bool is_neutral_for_all_traits = true; + bool is_neutral_for_direct_fitness_traits = true; + bool independent_dominance_for_all_traits = true; + bool independent_dominance_for_any_traits = false; + + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + MutationTraitInfo &traitInfoRec = mut_trait_info[trait_index]; + + if (traitInfoRec.effect_size_ != (slim_effect_t)0.0) + { + is_neutral_for_all_traits = false; + + if (traits[trait_index]->HasDirectFitnessEffect()) + is_neutral_for_direct_fitness_traits = false; + + if (std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) + independent_dominance_for_any_traits = true; + else + independent_dominance_for_all_traits = false; + } + } + + is_neutral_for_all_traits_ = is_neutral_for_all_traits; + is_neutral_for_direct_fitness_traits_ = is_neutral_for_direct_fitness_traits; + independent_dominance_for_all_traits_ = independent_dominance_for_all_traits; + independent_dominance_for_any_traits_ = independent_dominance_for_any_traits; +} + +void Mutation::SelfConsistencyCheck(const std::string &p_message_end) const { + // This mirrors Mutation::EvaluateFlags(); essentially it checks that flags are set as guaranteed by that method if (!mutation_type_ptr_) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) mutation_type_ptr_ is nullptr" << p_message_end << "." << EidosTerminate(); @@ -398,8 +458,10 @@ void Mutation::SelfConsistencyCheck(const std::string &p_message_end) const std::vector &traits = species.Traits(); slim_trait_index_t trait_count = species.TraitCount(); - bool all_neutral_effects = true; - bool all_neutral_direct_effects = true; + bool is_neutral_for_all_traits = true; + bool is_neutral_for_direct_fitness_traits = true; + bool independent_dominance_for_all_traits = true; + bool independent_dominance_for_any_traits = false; for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { @@ -408,15 +470,11 @@ void Mutation::SelfConsistencyCheck(const std::string &p_message_end) if (!std::isfinite(traitInfoRec.effect_size_)) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation effect size is non-finite" << p_message_end << "." << EidosTerminate(); - if (std::isinf(traitInfoRec.dominance_coeff_UNSAFE_)) // NAN is legal sometimes, checked below + if (std::isinf(traitInfoRec.dominance_coeff_UNSAFE_)) // NAN is legal EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation dominance is infinite" << p_message_end << "." << EidosTerminate(); if (!std::isfinite(traitInfoRec.hemizygous_dominance_coeff_)) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation hemizygous dominance is non-finite" << p_message_end << "." << EidosTerminate(); - if ((is_independent_dominance_ && !std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) || - (!is_independent_dominance_ && std::isnan(traitInfoRec.dominance_coeff_UNSAFE_))) - EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation independent dominance state is inconsistent" << p_message_end << "." << EidosTerminate(); - slim_effect_t effect_size = traitInfoRec.effect_size_; slim_effect_t dominance = RealizedDominanceForTrait(trait); // handle NAN for independent dominance slim_effect_t hemizygous_dominance = traitInfoRec.hemizygous_dominance_coeff_; @@ -442,25 +500,38 @@ void Mutation::SelfConsistencyCheck(const std::string &p_message_end) if (correct_hemizygous_effect != traitInfoRec.hemizygous_effect_) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): (internal error) hemizygous_effect_ does not match expectations" << p_message_end << "." << EidosTerminate(); - if (traitInfoRec.effect_size_ != (slim_effect_t)0.0) + if (effect_size != (slim_effect_t)0.0) { - all_neutral_effects = false; + is_neutral_for_all_traits = false; if (trait->HasDirectFitnessEffect()) - all_neutral_direct_effects = false; + is_neutral_for_direct_fitness_traits = false; + + if (std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) + independent_dominance_for_any_traits = true; + else + independent_dominance_for_all_traits = false; } } - if ((is_neutral_for_all_traits_ && !all_neutral_effects) || - (!is_neutral_for_all_traits_ && all_neutral_effects)) + if ((is_neutral_for_all_traits_ && !is_neutral_for_all_traits) || + (!is_neutral_for_all_traits_ && is_neutral_for_all_traits)) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation neutrality state is inconsistent" << p_message_end << "." << EidosTerminate(); - if ((is_neutral_for_direct_fitness_traits_ && !all_neutral_direct_effects) || - (!is_neutral_for_direct_fitness_traits_ && all_neutral_direct_effects)) + if ((is_neutral_for_direct_fitness_traits_ && !is_neutral_for_direct_fitness_traits) || + (!is_neutral_for_direct_fitness_traits_ && is_neutral_for_direct_fitness_traits)) EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): mutation direct-effect neutrality state is inconsistent" << p_message_end << "." << EidosTerminate(); + + if ((independent_dominance_for_all_traits && !independent_dominance_for_all_traits_) || + (!independent_dominance_for_all_traits && independent_dominance_for_all_traits_)) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): independent_dominance_for_all_traits_ state is inconsistent" << p_message_end << "." << EidosTerminate(); + + if ((independent_dominance_for_any_traits && !independent_dominance_for_any_traits_) || + (!independent_dominance_for_any_traits && independent_dominance_for_any_traits_)) + EIDOS_TERMINATION << "ERROR (Mutation::SelfConsistencyCheck): independent_dominance_for_any_traits_ state is inconsistent" << p_message_end << "." << EidosTerminate(); } -slim_effect_t Mutation::RealizedDominanceForTrait(Trait *p_trait) +slim_effect_t Mutation::RealizedDominanceForTrait(Trait *p_trait) const { slim_trait_index_t trait_index = p_trait->Index(); Species &species = mutation_type_ptr_->species_; @@ -532,43 +603,9 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * p_new_effect); // 2ha traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * p_new_effect); // 2ha (using h_hemi) } - - is_neutral_for_all_traits_ = false; - - if (p_trait->HasDirectFitnessEffect()) - is_neutral_for_direct_fitness_traits_ = false; } else // p_new_effect == 0.0; therefore, old_effect != 0.0 { - // Changing from non-neutral to neutral; determine whether the whole mutation is now neutral - // This is a bit complicated, but I don't expect this case to be hit very often - Species &species = mutation_type_ptr_->species_; - const std::vector &traits = species.Traits(); - MutationBlock *mutation_block = species.SpeciesMutationBlock(); - MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); - slim_trait_index_t trait_count = species.TraitCount(); - - is_neutral_for_all_traits_ = true; - is_neutral_for_direct_fitness_traits_ = true; - - for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) - { - MutationTraitInfo &info_for_trait = mut_trait_info[trait_index]; - - if (info_for_trait.effect_size_ != (slim_effect_t)0.0) - { - is_neutral_for_all_traits_ = false; - - if (traits[trait_index]->HasDirectFitnessEffect()) - is_neutral_for_direct_fitness_traits_ = false; - - break; - } - } - - // Note that we cannot set species.species_all_neutral_mutations_ and mutation_type_ptr_->all_neutral_mutations_ to - // false here, because only this mutation has changed to neutral; other mutations might be non-neutral - // cache values used by the fitness calculation code for speed; see header // for a neutral trait, we can set up this info very quickly if (p_trait->Type() == TraitType::kMultiplicative) @@ -585,6 +622,8 @@ void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_e } } + EvaluateFlags(); + // notify the species that the mutation has changed Species &species = mutation_type_ptr_->species_; @@ -596,13 +635,6 @@ void Mutation::SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, sli { traitInfoRec->dominance_coeff_UNSAFE_ = p_new_dominance; - // set the is_independent_dominance_ flag according to p_new_dominance; if this produces an inconsistency - // with dominance values for other traits, it will be caught by the consistency check at the end - if (std::isnan(p_new_dominance)) - is_independent_dominance_ = true; - else - is_independent_dominance_ = false; - // We only need to recache the heterozygous_effect_ value, since only it is affected by the change in // dominance coefficient. Changing dominance has no effect on is_neutral_for_all_traits_ or any of the // other is-neutral flags. So this is very simple. @@ -619,6 +651,8 @@ void Mutation::SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, sli traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); } + EvaluateFlags(); + // when mutation characteristics change, the species needs to be notified mutation_type_ptr_->species_.NoteChangedMutation(this); } @@ -640,6 +674,9 @@ void Mutation::SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitIn traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); } + // No need for this here; hemizygous dominance does not affect our flags + //EvaluateFlags(); + // when mutation characteristics change, the species needs to be notified mutation_type_ptr_->species_.NoteChangedMutation(this); } @@ -705,8 +742,6 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(mutation_id_)); case gID_isFixed: // ACCELERATED return (((state_ == MutationState::kFixedAndSubstituted) || (state_ == MutationState::kRemovedWithSubstitution)) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); - case gID_isIndependentDominance: // ACCELERATED - return (is_independent_dominance_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_isNeutral: // ACCELERATED return (is_neutral_for_all_traits_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_isSegregating: // ACCELERATED @@ -931,21 +966,6 @@ EidosValue *Mutation::GetProperty_Accelerated_isFixed(EidosGlobalStringID p_prop return logical_result; } -EidosValue *Mutation::GetProperty_Accelerated_isIndependentDominance(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) -{ -#pragma unused (p_property_id) - EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); - - for (size_t value_index = 0; value_index < p_values_size; ++value_index) - { - Mutation *value = (Mutation *)(p_values[value_index]); - - logical_result->set_logical_no_check(value->is_independent_dominance_, value_index); - } - - return logical_result; -} - EidosValue *Mutation::GetProperty_Accelerated_isNeutral(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { #pragma unused (p_property_id) @@ -1170,7 +1190,9 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << property_string << " is required to be finite." << EidosTerminate(); SetEffect(trait, traitInfoRec, new_effect); +#if DEBUG SelfConsistencyCheck(" after setting " + property_string); +#endif return; } } @@ -1188,7 +1210,9 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << new_dominance << " is required to be finite or NAN." << EidosTerminate(); SetHemizygousDominance(trait, traitInfoRec, new_dominance); +#if DEBUG SelfConsistencyCheck(" after setting " + property_string); +#endif return; } } @@ -1206,7 +1230,9 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << new_dominance << " is required to be finite or NAN." << EidosTerminate(); SetDominance(trait, traitInfoRec, new_dominance); +#if DEBUG SelfConsistencyCheck(" after setting " + property_string); +#endif return; } } @@ -1262,6 +1288,7 @@ EidosValue_SP Mutation::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, c case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_hemizygousDominanceForTrait: return ExecuteMethod_hemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_isIndependentDominanceForTrait: return ExecuteMethod_isIndependentDominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_setMutationType: return ExecuteMethod_setMutationType(p_method_id, p_arguments, p_interpreter); default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } @@ -1381,6 +1408,44 @@ EidosValue_SP Mutation::ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStr } } +// ********************* - (logical)isIndependentDominanceForTrait([Niso trait = NULL]) +// +EidosValue_SP Mutation::ExecuteMethod_isIndependentDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "isIndependentDominanceForTrait"); + + // get the trait info for this mutation + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + + if (trait_indices.size() == 1) + { + slim_trait_index_t trait_index = trait_indices[0]; + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_UNSAFE_; + + return (std::isnan(dominance) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + } + else + { + EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->reserve(trait_indices.size()); + + for (slim_trait_index_t trait_index : trait_indices) + { + slim_effect_t dominance = mut_trait_info[trait_index].dominance_coeff_UNSAFE_; + + logical_result->push_logical_no_check(std::isnan(dominance)); + } + + return EidosValue_SP(logical_result); + } +} + // ********************* - (void)setMutationType(io$ mutType) // EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -1430,7 +1495,6 @@ const std::vector *Mutation_Class::Properties(void) properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosome, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isFixed, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isFixed)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isIndependentDominance, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isIndependentDominance)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isNeutral, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isNeutral)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isSegregating, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isSegregating)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_mutationType)); @@ -1463,6 +1527,7 @@ const std::vector *Mutation_Class::Methods(void) const methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_isIndependentDominanceForTrait, kEidosValueMaskLogical))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); @@ -1669,8 +1734,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI else EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): setEffectForTrait() requires that effect be (a) NULL, requesting an effect value drawn from the mutation's mutation type for each trait, (b) singleton, providing one effect value for all traits, (c) equal in length to the number of traits in the species, providing one effect value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one effect value per trait per mutation." << EidosTerminate(); +#if DEBUG for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) mutations_buffer[mutation_index]->SelfConsistencyCheck(" after setEffectForTrait()"); +#endif return gStaticEidosValueVOID; } @@ -1886,8 +1953,10 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri else EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_" << method_name << "): " << method_name << "() requires that dominance be (a) NULL, requesting the default" << ((p_method_id == gID_setDominanceForTrait) ? " " : " hemizygous ") << "dominance coefficient from the mutation's mutation type for each trait, (b) singleton, providing one dominance value for all traits, (c) equal in length to the number of traits in the species, providing one dominance value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one dominance value per trait per mutation." << EidosTerminate(); +#if DEBUG for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) mutations_buffer[mutation_index]->SelfConsistencyCheck(std::string(" after ") + method_name); +#endif return gStaticEidosValueVOID; } diff --git a/core/mutation.h b/core/mutation.h index 6760aac9..133dcde9 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -112,15 +112,24 @@ class Mutation : public EidosDictionaryRetained // demand and calculate when fitness is being calculated. unsigned int is_neutral_for_direct_fitness_traits_ : 1; - // is_independent_dominance_ is true if the mutation has been configured to exhibit "independent dominance", - // meaning that two heterozygous effects equal one homozygous effect, allowing the effects from haplosomes - // to be calculated separately with no regard for zygosity; this is configured by using NAN as the default - // dominance coefficient for MutationType. It is updated if the state of the mutation's dominance changes, - // but only based upon the special NAN dominance value in setDominanceForTrait(); setting dominance values - // that happen to produce independent dominance does not cause this flag to be set, only the special NAN - // value. This is used to construct independent-dominance caches for fast trait evaluation. Note that this - // flag and is_neutral_for_all_traits_ can both be true, recording that independent dominance was configured. - unsigned int is_independent_dominance_ : 1; + // independent_dominance_for_all_traits_ is true if the mutation has been configured to exhibit "independent + // dominance" for ALL traits (among those that are non-neutral; traits for which the mutation is neutral are + // irrelevant for the determination of this flag). Independent dominance is configured by using NAN as the + // dominance coefficient for for the mutation, for a given trait. This flag is updated if the state of the + // mutation's dominance changes; it is not sticky. If this flag is set, the mutation can be omitted from + // non-neutral caches even if it is non-neutral, because all of its effects can be handled separately by the + // independent dominance mechanism. Note that this flag and is_neutral_for_all_traits_ can both be true, + // recording that independent dominance is configured even though all effects are presently neutral. Also + // note that if all effects for a mutation are neutral, this flag will be true. + unsigned int independent_dominance_for_all_traits_ : 1; + + // independent_dominance_for_any_traits_ is true if the mutation has been configured to exhibit "independent + // dominance" for ANY traits (among those that are non-neutral; traits for which the mutation is neutral are + // irrelevant for the determination of this flag). See independent_dominance_for_all_traits_. If this flag + // is set for a given trait across all mutations, SLiM will attempt to optimize for that trait being entirely + // independent dominance, allowing the calculation of phenotypes for that trait to be optimized. Note that + // if all effects for a mutation are neutral, this flag will be false. + unsigned int independent_dominance_for_any_traits_ : 1; int8_t nucleotide_; // the nucleotide being kept: A=0, C=1, G=2, T=3. -1 is used to indicate non-nucleotide-based. int8_t scratch_; // temporary scratch space for use by algorithms; regard as volatile outside your own code block @@ -163,11 +172,14 @@ class Mutation : public EidosDictionaryRetained virtual void SelfDelete(void) override; + // Re-evaluate our internal flags (neutrality, independent dominance) based upon current state + void EvaluateFlags(void); + // Check that our internal state all makes sense - void SelfConsistencyCheck(const std::string &p_message_end); + void SelfConsistencyCheck(const std::string &p_message_end) const; // This handles the possibility that a dominance coefficient is NAN, representing independent dominance, and returns the correct value - slim_effect_t RealizedDominanceForTrait(Trait *p_trait); + slim_effect_t RealizedDominanceForTrait(Trait *p_trait) const; // These should be called whenever a mutation effect/dominance is changed; they handle the necessary recaching void SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect); @@ -186,12 +198,12 @@ class Mutation : public EidosDictionaryRetained EidosValue_SP ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_isIndependentDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setMutationType(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_isFixed(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_isIndependentDominance(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_isNeutral(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_isSegregating(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 59a503c2..38a0b729 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -252,13 +252,8 @@ void MutationType::SelfConsistencyCheck(const std::string &p_message_end) if (effect_distributions_.size() > 0) { - bool is_independent_dominance = std::isnan(effect_distributions_[0].default_dominance_coeff_); - for (EffectDistributionInfo &des_info : effect_distributions_) { - if (std::isnan(des_info.default_dominance_coeff_) != is_independent_dominance) - EIDOS_TERMINATION << "ERROR (MutationType::SelfConsistencyCheck): mutation type independent dominance state is inconsistent" << p_message_end << "." << EidosTerminate(); - if (std::isinf(des_info.default_dominance_coeff_)) // NAN allowed EIDOS_TERMINATION << "ERROR (MutationType::SelfConsistencyCheck): mutation type default dominance is infinite" << p_message_end << "." << EidosTerminate(); diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index fd32a59a..879d5a18 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1245,7 +1245,6 @@ const std::string &gStr_mutationTypes = EidosRegisteredString("mutationTypes", g const std::string &gStr_mutationFractions = EidosRegisteredString("mutationFractions", gID_mutationFractions); const std::string &gStr_mutationMatrix = EidosRegisteredString("mutationMatrix", gID_mutationMatrix); const std::string &gStr_isFixed = EidosRegisteredString("isFixed", gID_isFixed); -const std::string &gStr_isIndependentDominance = EidosRegisteredString("isIndependentDominance", gID_isIndependentDominance); const std::string &gStr_isNeutral = EidosRegisteredString("isNeutral", gID_isNeutral); const std::string &gStr_isSegregating = EidosRegisteredString("isSegregating", gID_isSegregating); const std::string &gStr_mutationType = EidosRegisteredString("mutationType", gID_mutationType); @@ -1388,6 +1387,7 @@ const std::string &gStr_setMutationMatrix = EidosRegisteredString("setMutationMa const std::string &gStr_effectForTrait = EidosRegisteredString("effectForTrait", gID_effectForTrait); const std::string &gStr_dominanceForTrait = EidosRegisteredString("dominanceForTrait", gID_dominanceForTrait); const std::string &gStr_hemizygousDominanceForTrait = EidosRegisteredString("hemizygousDominanceForTrait", gID_hemizygousDominanceForTrait); +const std::string &gStr_isIndependentDominanceForTrait = EidosRegisteredString("isIndependentDominanceForTrait", gID_isIndependentDominanceForTrait); const std::string &gStr_setEffectForTrait = EidosRegisteredString("setEffectForTrait", gID_setEffectForTrait); const std::string &gStr_setDominanceForTrait = EidosRegisteredString("setDominanceForTrait", gID_setDominanceForTrait); const std::string &gStr_setHemizygousDominanceForTrait = EidosRegisteredString("setHemizygousDominanceForTrait", gID_setHemizygousDominanceForTrait); diff --git a/core/slim_globals.h b/core/slim_globals.h index 5ce49737..53d7012b 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -836,7 +836,6 @@ extern const std::string &gStr_mutationTypes; extern const std::string &gStr_mutationFractions; extern const std::string &gStr_mutationMatrix; extern const std::string &gStr_isFixed; -extern const std::string &gStr_isIndependentDominance; extern const std::string &gStr_isNeutral; extern const std::string &gStr_isSegregating; extern const std::string &gStr_mutationType; @@ -978,6 +977,7 @@ extern const std::string &gStr_setMutationMatrix; extern const std::string &gStr_effectForTrait; extern const std::string &gStr_dominanceForTrait; extern const std::string &gStr_hemizygousDominanceForTrait; +extern const std::string &gStr_isIndependentDominanceForTrait; extern const std::string &gStr_setEffectForTrait; extern const std::string &gStr_setDominanceForTrait; extern const std::string &gStr_setHemizygousDominanceForTrait; @@ -1324,7 +1324,6 @@ enum _SLiMGlobalStringID : int { gID_mutationFractions, gID_mutationMatrix, gID_isFixed, - gID_isIndependentDominance, gID_isNeutral, gID_isSegregating, gID_mutationType, @@ -1466,6 +1465,7 @@ enum _SLiMGlobalStringID : int { gID_effectForTrait, gID_dominanceForTrait, gID_hemizygousDominanceForTrait, + gID_isIndependentDominanceForTrait, gID_setEffectForTrait, gID_setDominanceForTrait, gID_setHemizygousDominanceForTrait, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index d8f3b361..ae5b588b 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1229,28 +1229,28 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('B','A'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(), c(0.75, 0.25))) stop(); }"); SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('B','A'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(c('B','A')), c(0.25, 0.75))) stop(); }"); SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(NAN, NAN)); if (identical(m1.defaultDominanceForTrait(), c(NAN, NAN))) stop(); }"); - SLiMAssertScriptRaise("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A'), NAN); }", "independent dominance state is inconsistent", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); }", "independent dominance state is inconsistent", __LINE__); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A'), NAN); if (identical(m1.defaultDominanceForTrait(), c(NAN, 0.5))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); if (identical(m1.defaultDominanceForTrait(), c(0.5, NAN))) stop(); }"); SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, 0.5)); if (identical(m1.defaultDominanceForTrait(), c(0.5, 0.5))) stop(); }"); - SLiMAssertScriptRaise("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A'), 0.5); }", "independent dominance state is inconsistent", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); }", "independent dominance state is inconsistent", __LINE__); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A'), 0.5); if (identical(m1.defaultDominanceForTrait(), c(0.5, NAN))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); if (identical(m1.defaultDominanceForTrait(), c(0.5, NAN))) stop(); }"); std::string middle = " initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeMutationRate(1e-4); } 1 late() { sim.addSubpop('p1', 10); } 2 late() { muts = sim.mutations; "; SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.isNeutral == T)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.isIndependentDominance == F)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.isIndependentDominanceForTrait(0) == F)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.dominance == 0.5)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.effect == 0.0)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.isNeutral == F)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.isIndependentDominance == F)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.isIndependentDominanceForTrait(0) == F)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.dominance == 0.5)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (allClose(muts.effect, 0.0001)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.isNeutral == T)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.isIndependentDominance == T)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.isIndependentDominanceForTrait(0) == T)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.dominance == 0.5)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.effect == 0.0)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (all(muts.isNeutral == F)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (all(muts.isIndependentDominance == T)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (all(muts.isIndependentDominanceForTrait(0) == T)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (allClose(muts.dominance, 0.4999875)) stop(); }"); // h = (sqrt(1+s)-1)/s SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (allClose(muts.effect, 0.0001)) stop(); }"); diff --git a/core/species.cpp b/core/species.cpp index 6c063329..cb403038 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -253,19 +253,45 @@ void Species::AutogenerationConfigurationChanged(void) mutation_type_ptr->muttype_all_neutral_mutations_ = false; } - // Then we loop through the GETypes and determine whether the species has any non-neutral mutations; it is - // assumed that if a GEType uses a non-neutral mutation type the species is non-neutral. Normally this - // flag is sticky, but if there are no segregating mutations we can reset it and recover neutrality. + // Then we loop through the GETypes and determine whether there are mutation types in use by a GEType + // that are non-neutral, and if so, set the appropriate optimization flags. Normally these flags are + // sticky, but if there are no segregating mutations we can reset them and recover neutrality. if (registry_count == 0) + { species_all_neutral_mutations_ = true; + + for (Trait *trait : traits_) + { + trait->trait_all_neutral_mutations_ = true; + trait->trait_all_mutations_independent_dominance_ = true; + } + } for (auto ge_type_iter : genomic_element_types_) { GenomicElementType *ge_type_ptr = ge_type_iter.second; for (MutationType *mutation_type_ptr : ge_type_ptr->mutation_type_ptrs_) + { if (!mutation_type_ptr->all_neutral_DES_) + { species_all_neutral_mutations_ = false; + + for (Trait *trait : traits_) + { + slim_trait_index_t trait_index = trait->Index(); + EffectDistributionInfo &DES_info = mutation_type_ptr->effect_distributions_[trait_index]; + + if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) + { + trait->trait_all_neutral_mutations_ = false; + + if (!std::isnan(DES_info.default_dominance_coeff_)) + trait->trait_all_mutations_independent_dominance_ = false; + } + } + } + } } // These flags cause a refresh of the user interface in SLiMgui. We don't know for sure here that these @@ -279,6 +305,7 @@ void Species::CheckOptimizationFlags(void) // This is called to check that the various optimization flags set by AutogenerationConfigurationChanged() // are in the correct state; incorrect results will be produced if the optimization flags are wrong! + // First check that all mutation types are tracked correctly for (auto mut_type_iter : mutation_types_) { MutationType *mutation_type_ptr = mut_type_iter.second; @@ -287,11 +314,11 @@ void Species::CheckOptimizationFlags(void) { if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) if (mutation_type_ptr->all_neutral_DES_ != false) - EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) all_neutral_DES_ is incorrect." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) all_neutral_DES_ is incorrect (a DES is non-neutral)." << EidosTerminate(); if (DES_info.DES_type_ == DESType::kScript) if (type_s_DESs_present_ != true) - EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) type_s_DESs_present_ is incorrect." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) type_s_DESs_present_ is incorrect (a DES uses type 's')." << EidosTerminate(); } // recalculate whether the mutation type has any non-neutral mutations associated with it @@ -299,17 +326,118 @@ void Species::CheckOptimizationFlags(void) // is true, but if there are no mutations segregating we can reset this flag and recover neutrality if (!mutation_type_ptr->all_neutral_DES_) if (mutation_type_ptr->muttype_all_neutral_mutations_ != false) - EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) muttype_all_neutral_mutations_ is incorrect." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) muttype_all_neutral_mutations_ is incorrect (the mutation type DES is non-neutral)." << EidosTerminate(); } + // Then check that mutation types used by genomic element types have the correct effect on flags for (auto ge_type_iter : genomic_element_types_) { GenomicElementType *ge_type_ptr = ge_type_iter.second; for (MutationType *mutation_type_ptr : ge_type_ptr->mutation_type_ptrs_) + { if (!mutation_type_ptr->all_neutral_DES_) + { + if (species_all_neutral_mutations_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) species_all_neutral_mutations_ is incorrect (a non-neutral mutation type is in use)." << EidosTerminate(); + + for (Trait *trait : traits_) + { + slim_trait_index_t trait_index = trait->Index(); + EffectDistributionInfo &DES_info = mutation_type_ptr->effect_distributions_[trait_index]; + + if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) + { + if (trait->trait_all_neutral_mutations_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) trait_all_neutral_mutations_ is incorrect (a mutation type with a non-neutral DES for the trait is in use)." << EidosTerminate(); + + if (!std::isnan(DES_info.default_dominance_coeff_)) + if (trait->trait_all_mutations_independent_dominance_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) trait_all_mutations_independent_dominance_ is incorrect (a mutation type with a non-neutral non-independent DES for the trait is in use)." << EidosTerminate(); + } + } + } + } + } + + // Finally, check that all mutations in the registry are compatible with the flag settings. Note that this + // state is taken care of by _NoteNonNeutralMutation(), not by AutogenerationConfigurationChanged(). + int registry_count = 0; + const MutationIndex *registry_iter = population_.MutationRegistry(®istry_count); + + if (registry_count > 0) + { + MutationBlock *mutation_block = mutation_block_; + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + + for (int registry_index = 0; registry_index < registry_count; ++registry_index) + { + const Mutation *mut = mut_block_ptr + registry_iter[registry_index]; + + mut->SelfConsistencyCheck(" in Species::CheckOptimizationFlags()"); + + if (!mut->is_neutral_for_all_traits_) + { if (species_all_neutral_mutations_ != false) - EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) species_all_neutral_mutations_ is incorrect." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) species_all_neutral_mutations_ is incorrect (a non-neutral mutation is segregating)." << EidosTerminate(); + + if (mut->mutation_type_ptr_->muttype_all_neutral_mutations_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) muttype_all_neutral_mutations_ is incorrect (a non-neutral mutation belongs to this mutation type)." << EidosTerminate(); + + // check Trait flags against this mutation + MutationTraitInfo *mut_trait_info = mutation_block_->TraitInfoForMutation(mut); + + for (Trait *trait : traits_) + { + slim_trait_index_t trait_index = trait->Index(); + MutationTraitInfo &trait_info = mut_trait_info[trait_index]; + + if (trait_info.effect_size_ != 0.0) + { + // this mutation is non-neutral for this trait + if (trait->trait_all_neutral_mutations_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) trait_all_neutral_mutations_ is incorrect (a mutation has a non-neutral effect for this trait)." << EidosTerminate(); + + if (!std::isnan(trait_info.dominance_coeff_UNSAFE_)) + { + // this mutation is non-neutral *and* not independent-dominance + if (trait->trait_all_mutations_independent_dominance_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) trait_all_mutations_independent_dominance_ is incorrect (a mutation has a non-neutral, non-independent effect for this trait)." << EidosTerminate(); + } + } + } + } + } + } +} + +void Species::_NoteNonNeutralMutation(const Mutation *p_mut) +{ + // This is a fair bit of work, but it is only done once when a new non-neutral mutation is created, or a + // mutation is changed to a non-neutral state. That is not very frequent compared to other overhead. + + species_all_neutral_mutations_ = false; + p_mut->mutation_type_ptr_->muttype_all_neutral_mutations_ = false; + + // since the mutation is non-neutral for at least one trait, we have to fix the Trait flags + MutationTraitInfo *mut_trait_info = mutation_block_->TraitInfoForMutation(p_mut); + + for (Trait *trait : traits_) + { + slim_trait_index_t trait_index = trait->Index(); + MutationTraitInfo &trait_info = mut_trait_info[trait_index]; + + if (trait_info.effect_size_ != 0.0) + { + // this mutation is non-neutral for this trait + trait->trait_all_neutral_mutations_ = false; + + if (!std::isnan(trait_info.dominance_coeff_UNSAFE_)) + { + // this mutation is non-neutral *and* not independent-dominance + trait->trait_all_mutations_independent_dominance_ = false; + } + } } } diff --git a/core/species.h b/core/species.h index efe36f66..de2ee0f2 100644 --- a/core/species.h +++ b/core/species.h @@ -436,7 +436,7 @@ class Species : public EidosDictionaryUnretained void AutogenerationConfigurationChanged(void); // called when a GenomicElementType / MutationType is changed void CheckOptimizationFlags(void); // essentially checks what AutogenerationConfigurationChanged() does - inline __attribute__((always_inline)) void NoteAutogeneratedMutation(Mutation *p_mut) + inline __attribute__((always_inline)) void NoteAutogeneratedMutation(const Mutation *p_mut) { // NoteAutogeneratedMutation() should be called whenever a new mutation is auto-generated by SLiM. // This is a different path from NoteChangedMutation() because the characteristics of these mutations @@ -452,24 +452,18 @@ class Species : public EidosDictionaryUnretained } } - inline __attribute__((always_inline)) void NoteChangedMutation(Mutation *p_mut) + void _NoteNonNeutralMutation(const Mutation *p_mut); + inline __attribute__((always_inline)) void NoteChangedMutation(const Mutation *p_mut) { // NoteChangedMutation() should be called whenever a mutation is added/changed in a way that is not // driven by SLiM's auto-generation configuration (i.e., GenomicElementType and MutationType). The // characteristics of this kind of mutation are not known, so they have to be examined closely. if (!p_mut->is_neutral_for_all_traits_) - { - species_all_neutral_mutations_ = false; - p_mut->mutation_type_ptr_->muttype_all_neutral_mutations_ = false; - - p_mut->mutation_type_ptr_->species_.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags - } - else - { - // This needs to be done in the neutral case too; for example, a non-neutral mutation might - // have been changed into a neutral mutation, which invalidates our non-neutral caches - p_mut->mutation_type_ptr_->species_.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags - } + _NoteNonNeutralMutation(p_mut); + + // This needs to be done in the neutral case too; for example, a non-neutral mutation might + // have been changed into a neutral mutation, which invalidates our non-neutral caches + p_mut->mutation_type_ptr_->species_.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags } // Chromosome configuration and access diff --git a/core/substitution.cpp b/core/substitution.cpp index f99b7655..d7a8dedd 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -37,7 +37,7 @@ #pragma mark - Substitution::Substitution(Mutation &p_mutation, slim_tick_t p_fixation_tick) : -EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), is_neutral_for_all_traits_(p_mutation.is_neutral_for_all_traits_), is_neutral_for_direct_fitness_traits_(p_mutation.is_neutral_for_direct_fitness_traits_), is_independent_dominance_(p_mutation.is_independent_dominance_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) +EidosDictionaryRetained(), mutation_type_ptr_(p_mutation.mutation_type_ptr_), position_(p_mutation.position_), subpop_index_(p_mutation.subpop_index_), origin_tick_(p_mutation.origin_tick_), fixation_tick_(p_fixation_tick), chromosome_index_(p_mutation.chromosome_index_), is_neutral_for_all_traits_(p_mutation.is_neutral_for_all_traits_), is_neutral_for_direct_fitness_traits_(p_mutation.is_neutral_for_direct_fitness_traits_), independent_dominance_for_all_traits_(p_mutation.independent_dominance_for_all_traits_), independent_dominance_for_any_traits_(p_mutation.independent_dominance_for_any_traits_), nucleotide_(p_mutation.nucleotide_), mutation_id_(p_mutation.mutation_id_), tag_value_(p_mutation.tag_value_) { AddKeysAndValuesFrom(&p_mutation); @@ -73,12 +73,6 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ trait_info_ = (SubstitutionTraitInfo *)malloc(trait_count * sizeof(SubstitutionTraitInfo)); - // We need to infer the values of the is_neutral_for_all_traits_ and is_independent_dominance_ flags - // FIXME MULTITRAIT: needs to be fixed when the below issues are fixed - is_neutral_for_all_traits_ = (p_selection_coeff == (slim_effect_t)0.0); - is_neutral_for_direct_fitness_traits_ = is_neutral_for_all_traits_; // FIXME MULTITRAIT - is_independent_dominance_ = std::isnan(p_dominance_coeff); - trait_info_[0].effect_size_ = p_selection_coeff; trait_info_[0].dominance_coeff_UNSAFE_ = p_dominance_coeff; // can be NAN trait_info_[0].hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(0); // FIXME MULTITRAIT: needs to be passed in @@ -90,12 +84,51 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ trait_info_[trait_index].hemizygous_dominance_coeff_ = 1.0; // FIXME MULTITRAIT: needs to be passed in } + EvaluateFlags(); + #if DEBUG SelfConsistencyCheck(" in Substitution::Substitution()"); #endif } -void Substitution::SelfConsistencyCheck(const std::string &p_message_end) +void Substitution::EvaluateFlags(void) +{ + // This mirrors Substitution::SelfConsistencyCheck(); essentially it sets up flags as expected by that method. + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + slim_trait_index_t trait_count = species.TraitCount(); + + // keep flags in local variables until we are ready to set their final values + bool is_neutral_for_all_traits = true; + bool is_neutral_for_direct_fitness_traits = true; + bool independent_dominance_for_all_traits = true; + bool independent_dominance_for_any_traits = false; + + for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) + { + SubstitutionTraitInfo &traitInfoRec = trait_info_[trait_index]; + + if (traitInfoRec.effect_size_ != (slim_effect_t)0.0) + { + is_neutral_for_all_traits = false; + + if (traits[trait_index]->HasDirectFitnessEffect()) + is_neutral_for_direct_fitness_traits = false; + + if (std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) + independent_dominance_for_any_traits = true; + else + independent_dominance_for_all_traits = false; + } + } + + is_neutral_for_all_traits_ = is_neutral_for_all_traits; + is_neutral_for_direct_fitness_traits_ = is_neutral_for_direct_fitness_traits; + independent_dominance_for_all_traits_ = independent_dominance_for_all_traits; + independent_dominance_for_any_traits_ = independent_dominance_for_any_traits; +} + +void Substitution::SelfConsistencyCheck(const std::string &p_message_end) const { if (!mutation_type_ptr_) EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): (internal error) mutation_type_ptr_ is nullptr" << p_message_end << "." << EidosTerminate(); @@ -107,6 +140,8 @@ void Substitution::SelfConsistencyCheck(const std::string &p_message_end) slim_trait_index_t trait_count = species.TraitCount(); bool all_neutral_effects = true; bool all_neutral_direct_effects = true; + bool independent_dominance_for_all_traits = true; + bool independent_dominance_for_any_traits = false; for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { @@ -115,21 +150,22 @@ void Substitution::SelfConsistencyCheck(const std::string &p_message_end) if (!std::isfinite(traitInfoRec.effect_size_)) EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution effect size is non-finite" << p_message_end << "." << EidosTerminate(); - if (std::isinf(traitInfoRec.dominance_coeff_UNSAFE_)) // NAN is legal sometimes, checked below + if (std::isinf(traitInfoRec.dominance_coeff_UNSAFE_)) // NAN is legal EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution dominance is infinite" << p_message_end << "." << EidosTerminate(); if (!std::isfinite(traitInfoRec.hemizygous_dominance_coeff_)) EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution hemizygous dominance is non-finite" << p_message_end << "." << EidosTerminate(); - if ((is_independent_dominance_ && !std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) || - (!is_independent_dominance_ && std::isnan(traitInfoRec.dominance_coeff_UNSAFE_))) - EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution independent dominance state is inconsistent" << p_message_end << "." << EidosTerminate(); - if (traitInfoRec.effect_size_ != (slim_effect_t)0.0) { all_neutral_effects = false; if (trait->HasDirectFitnessEffect()) all_neutral_direct_effects = false; + + if (std::isnan(traitInfoRec.dominance_coeff_UNSAFE_)) + independent_dominance_for_any_traits = true; + else + independent_dominance_for_all_traits = false; } } @@ -140,6 +176,14 @@ void Substitution::SelfConsistencyCheck(const std::string &p_message_end) if ((is_neutral_for_direct_fitness_traits_ && !all_neutral_direct_effects) || (!is_neutral_for_direct_fitness_traits_ && all_neutral_direct_effects)) EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): substitution direct-effect neutrality state is inconsistent" << p_message_end << "." << EidosTerminate(); + + if ((independent_dominance_for_all_traits && !independent_dominance_for_all_traits_) || + (!independent_dominance_for_all_traits && independent_dominance_for_all_traits_)) + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): independent_dominance_for_all_traits_ state is inconsistent" << p_message_end << "." << EidosTerminate(); + + if ((independent_dominance_for_any_traits && !independent_dominance_for_any_traits_) || + (!independent_dominance_for_any_traits && independent_dominance_for_any_traits_)) + EIDOS_TERMINATION << "ERROR (Substitution::SelfConsistencyCheck): independent_dominance_for_any_traits_ state is inconsistent" << p_message_end << "." << EidosTerminate(); } slim_effect_t Substitution::RealizedDominanceForTrait(Trait *p_trait) @@ -310,8 +354,6 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) } case gID_id: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(mutation_id_)); - case gID_isIndependentDominance: // ACCELERATED - return (is_independent_dominance_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_isNeutral: // ACCELERATED return (is_neutral_for_all_traits_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); case gID_mutationType: // ACCELERATED @@ -507,21 +549,6 @@ EidosValue *Substitution::GetProperty_Accelerated_id(EidosGlobalStringID p_prope return int_result; } -EidosValue *Substitution::GetProperty_Accelerated_isIndependentDominance(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) -{ -#pragma unused (p_property_id) - EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->resize_no_initialize(p_values_size); - - for (size_t value_index = 0; value_index < p_values_size; ++value_index) - { - Substitution *value = (Substitution *)(p_values[value_index]); - - logical_result->set_logical_no_check(value->is_independent_dominance_, value_index); - } - - return logical_result; -} - EidosValue *Substitution::GetProperty_Accelerated_isNeutral(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { #pragma unused (p_property_id) @@ -710,6 +737,7 @@ EidosValue_SP Substitution::ExecuteInstanceMethod(EidosGlobalStringID p_method_i case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_hemizygousDominanceForTrait: return ExecuteMethod_hemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); + case gID_isIndependentDominanceForTrait: return ExecuteMethod_isIndependentDominanceForTrait(p_method_id, p_arguments, p_interpreter); default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } } @@ -819,6 +847,40 @@ EidosValue_SP Substitution::ExecuteMethod_hemizygousDominanceForTrait(EidosGloba } } +// ********************* - (logical)isIndependentDominanceForTrait([Niso trait = NULL]) +// +EidosValue_SP Substitution::ExecuteMethod_isIndependentDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "isIndependentDominanceForTrait"); + + if (trait_indices.size() == 1) + { + slim_trait_index_t trait_index = trait_indices[0]; + slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_UNSAFE_; + + return (std::isnan(dominance) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalT); + } + else + { + EidosValue_Logical *logical_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Logical())->reserve(trait_indices.size()); + + for (slim_trait_index_t trait_index : trait_indices) + { + slim_effect_t dominance = trait_info_[trait_index].dominance_coeff_UNSAFE_; + + logical_result->push_logical_no_check(std::isnan(dominance)); + } + + return EidosValue_SP(logical_result); + } +} + // // Substitution_Class @@ -842,7 +904,6 @@ const std::vector *Substitution_Class::Properties(vo properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosome, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_id)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isIndependentDominance, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isIndependentDominance)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isNeutral, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isNeutral)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_mutationType)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_position)); @@ -875,6 +936,7 @@ const std::vector *Substitution_Class::Methods(void) c methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_isIndependentDominanceForTrait, kEidosValueMaskLogical))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/substitution.h b/core/substitution.h index db77f5ba..ed851adb 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -75,7 +75,8 @@ class Substitution : public EidosDictionaryRetained unsigned int is_neutral_for_all_traits_ : 1; // all effects are 0.0; see mutation.h unsigned int is_neutral_for_direct_fitness_traits_ : 1; // all effects for direct fitness traits are 0.0 - unsigned int is_independent_dominance_ : 1; // configured for "independent dominance"; see mutation.h + unsigned int independent_dominance_for_all_traits_ : 1; // all non-neutral traits configured for "independent dominance"; see mutation.h + unsigned int independent_dominance_for_any_traits_ : 1; // any non-neutral traits configured for "independent dominance"; see mutation.h int8_t nucleotide_; // A=0, C=1, G=2, T=3. -1 means non-nucleotide-based. const slim_mutationid_t mutation_id_; // a unique id for each mutation, to track mutations @@ -92,8 +93,11 @@ class Substitution : public EidosDictionaryRetained inline virtual ~Substitution(void) override { free(trait_info_); trait_info_ = nullptr; } + // Re-evaluate our internal flags (neutrality, independent dominance) based upon current state + void EvaluateFlags(void); + // Check that our internal state all makes sense - void SelfConsistencyCheck(const std::string &p_message_end); + void SelfConsistencyCheck(const std::string &p_message_end) const; // This handles the possibility that a dominance coefficient is NAN, representing independent dominance, and returns the correct value slim_effect_t RealizedDominanceForTrait(Trait *p_trait); @@ -113,10 +117,10 @@ class Substitution : public EidosDictionaryRetained EidosValue_SP ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_isIndependentDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); - static EidosValue *GetProperty_Accelerated_isIndependentDominance(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_isNeutral(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_nucleotide(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_nucleotideValue(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); diff --git a/core/trait.h b/core/trait.h index b390ff78..690d3327 100644 --- a/core/trait.h +++ b/core/trait.h @@ -78,6 +78,16 @@ class Trait : public EidosDictionaryRetained // a user-defined tag value slim_usertag_t tag_value_ = SLIM_TAG_UNSET_VALUE; + // OPTIMIZATION FLAGS + + // this is set to false if any mutation has a non-neutral effect on this trait + bool trait_all_neutral_mutations_ = true; + + // this is set to false if any mutation has a non-neutral effect on this trait that + // is not designated as "independent dominance"; neutral effects don't matter + bool trait_all_mutations_independent_dominance_ = true; + + Trait(const Trait&) = delete; // no copying Trait& operator=(const Trait&) = delete; // no copying Trait(void) = delete; // no null constructor From a1ca364e698ab9d4f5ac0b1eb4e82585acf0560f Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 7 Jan 2026 16:15:54 -0600 Subject: [PATCH 071/107] add dynamic properties to the SLiMgui docs --- EidosScribe/EidosCocoaExtra.mm | 9 ++ EidosScribe/EidosHelpController.mm | 132 +++++++++++++++++------- QtSLiM/QtSLiMExtras.cpp | 13 +++ QtSLiM/QtSLiMHelpWindow.cpp | 133 ++++++++++++++++++------- QtSLiM/help/SLiMHelpClasses.html | 20 +++- SLiMgui/SLiMHelpClasses.rtf | 155 +++++++++++++++++++++++++---- VERSIONS | 1 + core/mutation.cpp | 3 +- 8 files changed, 368 insertions(+), 98 deletions(-) diff --git a/EidosScribe/EidosCocoaExtra.mm b/EidosScribe/EidosCocoaExtra.mm index 492a2224..eb0971b5 100644 --- a/EidosScribe/EidosCocoaExtra.mm +++ b/EidosScribe/EidosCocoaExtra.mm @@ -223,6 +223,15 @@ + (NSAttributedString *)eidosAttributedStringForPropertySignature:(const EidosPr [attrStr addAttribute:NSBaselineOffsetAttributeName value:[NSNumber numberWithFloat:2.0] range:NSMakeRange(0, [attrStr length])]; + // BCH 1/7/2025: For dynamic properties with a name like "Effect", italicize the portion in the <> + if ([propertyNameString hasPrefix:@"<"]) + { + NSRange endRange = [propertyNameString rangeOfString:@">"]; + + if (endRange.location != NSNotFound) + [attrStr applyFontTraits:NSItalicFontMask range:NSMakeRange(0, endRange.location)]; + } + return attrStr; } diff --git a/EidosScribe/EidosHelpController.mm b/EidosScribe/EidosHelpController.mm index 4c1b204a..de6b9c4d 100644 --- a/EidosScribe/EidosHelpController.mm +++ b/EidosScribe/EidosHelpController.mm @@ -320,7 +320,7 @@ - (void)addTopicsFromRTFFile:(NSString *)rtfFile underHeading:(NSString *)topLev NSRegularExpression *topicGenericItemRegex = [NSRegularExpression regularExpressionWithPattern:@"^((?:[0-9]+\\.)*[0-9]+)\\.? ITEM: ((?:[0-9]+\\.? )?)(.+)$" options:NSRegularExpressionCaseInsensitive error:NULL]; // 3 captures NSRegularExpression *topicFunctionRegex = [NSRegularExpression regularExpressionWithPattern:@"^\\([a-zA-Z<>\\*+$]+\\)([a-zA-Z_0-9]+)\\(.+$" options:NSRegularExpressionCaseInsensitive error:NULL]; // 1 capture NSRegularExpression *topicMethodRegex = [NSRegularExpression regularExpressionWithPattern:@"^([-–+])[ \u00A0]\\([a-zA-Z<>\\*+$]+\\)([a-zA-Z_0-9]+)\\(.+$" options:NSRegularExpressionCaseInsensitive error:NULL]; // 2 captures - NSRegularExpression *topicPropertyRegex = [NSRegularExpression regularExpressionWithPattern:@"^([a-zA-Z_0-9]+)[ \u00A0]((?:<[-–]>)|(?:=>)) \\([a-zA-Z<>\\*+$]+\\)$" options:NSRegularExpressionCaseInsensitive error:NULL]; // 2 captures + NSRegularExpression *topicPropertyRegex = [NSRegularExpression regularExpressionWithPattern:@"^([a-zA-Z_0-9<>-]+)[ \u00A0]((?:<[-–]>)|(?:=>)) (\\([a-zA-Z<>\\*+$]+\\))$" options:NSRegularExpressionCaseInsensitive error:NULL]; // 3 captures // Scan through the file one line at a time, parsing out topic headers NSString *topicFileString = [topicFileAttrString string]; @@ -537,49 +537,108 @@ - (void)addTopicsFromRTFFile:(NSString *)rtfFile underHeading:(NSString *)topLev NSString *callName = [line substringWithRange:callNameRange]; NSRange readOnlyRange = [match rangeAtIndex:2]; NSString *readOnlyName = [line substringWithRange:readOnlyRange]; + NSRange resultRange = [match rangeAtIndex:3]; + NSString *resultName = [line substringWithRange:resultRange]; //NSLog(@"topic property name: %@, line: %@", callName, line); - // Check for a built-in property signature that matches and substitute it in. Note that we accept a match from any property in any class - // API as long as the signature matches; we do not rigorously check that the API within a given class matches between signature and doc. - // This is mostly not a problem because it is quite rare for the same property name to be used with more than one signature. - if (propertyList) + if ([callName hasPrefix:@"<"]) { + // BCH 1/7/2026: This case is hit for dynamic properties, which are present in the doc with names like Effect. We don't want to + // check for a match, we just want to accept the property as it is. We do want to reformat it, though; we make a temporary signature for that. std::string property_name([callName UTF8String]); - bool found_match = false, found_mismatch = false; - NSString *oldSignatureString = nullptr; - NSString *newSignatureString = nullptr; + bool property_read_only = true; - for (auto signature_iter = propertyList->begin(); signature_iter != propertyList->end(); signature_iter++) - if ((*signature_iter)->property_name_.compare(property_name) == 0) - { - EidosPropertySignature_CSP candidate_signature = *signature_iter; - NSAttributedString *attrSig = [NSAttributedString eidosAttributedStringForPropertySignature:candidate_signature.get() size:11.0]; - - oldSignatureString = [lineAttrString string]; - newSignatureString = [attrSig string]; - - if ([oldSignatureString isEqualToString:newSignatureString]) + if ([readOnlyName isEqualToString:@"=>"]) + property_read_only = true; + else if ([readOnlyName isEqualToString:@"<->"] || [readOnlyName isEqualToString:@"<–>"]) + property_read_only = false; + else + NSLog(@"*** unrecognized readOnlyName %@ parsing %@", readOnlyName, callName); + + EidosValueMask property_mask = kEidosValueMaskInt | kEidosValueMaskSingleton; + NSString *property_class_name = @""; + + if ([resultName isEqualToString:@"(float$)"]) + property_mask = kEidosValueMaskFloat | kEidosValueMaskSingleton; + else if ([resultName isEqualToString:@"(object$)"]) + { + property_mask = kEidosValueMaskObject | kEidosValueMaskSingleton; + property_class_name = @"Trait"; + } + else + NSLog(@"*** unrecognized resultName %@ parsing %@", resultName, callName); + + EidosPropertySignature_CSP dynamic_signature = EidosPropertySignature_CSP(new EidosPropertySignature(property_name, property_read_only, property_mask)); + NSMutableAttributedString *attrSig = [[[NSAttributedString eidosAttributedStringForPropertySignature:dynamic_signature.get() size:11.0] mutableCopy] autorelease]; + + // substitute in a class name if we have one; the classes are in SLiM so we don't have access to them here + if ([property_class_name length] > 0) + { + NSRange originalRange = [[attrSig string] rangeOfString:@"object"]; + + if (originalRange.location != NSNotFound) + [attrSig replaceCharactersInRange:originalRange withString:[NSString stringWithFormat:@"object<%@>", property_class_name]]; + } + + NSString *oldSignatureString = [lineAttrString string]; + NSString *newSignatureString = [attrSig string]; + + if ([oldSignatureString isEqualToString:newSignatureString]) + { + //NSLog(@"signature match for method %@", callName); + + // Replace the signature line from the RTF file with the syntax-colored version + lineAttrString = attrSig; + } + else + { + NSLog(@"*** signature mismatch for dynamic property %@", callName); + } + } + else + { + // Check for a built-in property signature that matches and substitute it in. Note that we accept a match from any property in any class + // API as long as the signature matches; we do not rigorously check that the API within a given class matches between signature and doc. + // This is mostly not a problem because it is quite rare for the same property name to be used with more than one signature. + if (propertyList) + { + std::string property_name([callName UTF8String]); + bool found_match = false, found_mismatch = false; + NSString *oldSignatureString = nullptr; + NSString *newSignatureString = nullptr; + + for (auto signature_iter = propertyList->begin(); signature_iter != propertyList->end(); signature_iter++) + if ((*signature_iter)->property_name_.compare(property_name) == 0) { - //NSLog(@"signature match for method %@", callName); + EidosPropertySignature_CSP candidate_signature = *signature_iter; + NSAttributedString *attrSig = [NSAttributedString eidosAttributedStringForPropertySignature:candidate_signature.get() size:11.0]; - // Replace the signature line from the RTF file with the syntax-colored version - lineAttrString = attrSig; - found_match = true; - break; - } - else - { - // If we find a mismatched signature but no matching signature, that's probably an error in either the doc or - // the signature, unless we find a match later on with a different signature for the same property name. - found_mismatch = true; + oldSignatureString = [lineAttrString string]; + newSignatureString = [attrSig string]; + + if ([oldSignatureString isEqualToString:newSignatureString]) + { + //NSLog(@"signature match for method %@", callName); + + // Replace the signature line from the RTF file with the syntax-colored version + lineAttrString = attrSig; + found_match = true; + break; + } + else + { + // If we find a mismatched signature but no matching signature, that's probably an error in either the doc or + // the signature, unless we find a match later on with a different signature for the same property name. + found_mismatch = true; + } } - } - - if (found_mismatch && !found_match) - NSLog(@"*** property signature mismatch:\nold: %@\nnew: %@", oldSignatureString, newSignatureString); - else if (!found_match) - NSLog(@"*** no property signature found for property name %@", callName); + + if (found_mismatch && !found_match) + NSLog(@"*** property signature mismatch:\nold: %@\nnew: %@", oldSignatureString, newSignatureString); + else if (!found_match) + NSLog(@"*** no property signature found for property name %@", callName); + } } topicItemKey = [NSString stringWithFormat:@"%@ %@", callName, readOnlyName]; @@ -660,6 +719,9 @@ - (void)checkDocumentationOfClass:(EidosClass *)classObject } } + // BCH 1/7/2026: Remove dynamic properties like "Effect" from the excess documentation list; they are not expected to have a match + [docProperties filterUsingPredicate:[NSPredicate predicateWithFormat:@"! SELF beginswith '<'"]]; + if ([docProperties count]) NSLog(@"*** excess documentation found for class %@ properties %@", classString, docProperties); diff --git a/QtSLiM/QtSLiMExtras.cpp b/QtSLiM/QtSLiMExtras.cpp index 02778b53..097b22d9 100644 --- a/QtSLiM/QtSLiMExtras.cpp +++ b/QtSLiM/QtSLiMExtras.cpp @@ -624,6 +624,19 @@ void ColorizePropertySignature(const EidosPropertySignature *property_signature, typeCursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1); typeCursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, typeLength); typeCursor.setCharFormat(typeAttrs); + + // BCH 1/7/2025: For dynamic properties with a name like "Effect", italicize the portion in the <> + if (Eidos_string_hasPrefix(property_signature->property_name_, "<")) + { + size_t end_position = property_signature->property_name_.find_first_of('>'); + QTextCharFormat italicTypeAttrs(functionAttrs); + italicTypeAttrs.setFontItalic(true); + + QTextCursor dynamicCursor(lineCursor); + dynamicCursor.setPosition(lineCursor.anchor(), QTextCursor::MoveAnchor); + dynamicCursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, static_cast(end_position)); + dynamicCursor.setCharFormat(italicTypeAttrs); + } } void ColorizeCallSignature(const EidosCallSignature *call_signature, double pointSize, QTextCursor lineCursor) diff --git a/QtSLiM/QtSLiMHelpWindow.cpp b/QtSLiM/QtSLiMHelpWindow.cpp index 3793147e..3fea2ad3 100644 --- a/QtSLiM/QtSLiMHelpWindow.cpp +++ b/QtSLiM/QtSLiMHelpWindow.cpp @@ -672,7 +672,7 @@ void QtSLiMHelpWindow::addTopicsFromRTFFile(const QString &htmlFile, static const QRegularExpression topicGenericItemRegex("^((?:[0-9]+\\.)*[0-9]+)\\.?[\u00A0 ] ITEM: ((?:[0-9]+\\.? )?)(.+)$", QRegularExpression::CaseInsensitiveOption); static const QRegularExpression topicFunctionRegex("^\\([a-zA-Z<>\\*+$]+\\)([a-zA-Z_0-9]+)\\(.+$", QRegularExpression::CaseInsensitiveOption); static const QRegularExpression topicMethodRegex("^([-–+])[\u00A0 ]\\([a-zA-Z<>\\*+$]+\\)([a-zA-Z_0-9]+)\\(.+$", QRegularExpression::CaseInsensitiveOption); - static const QRegularExpression topicPropertyRegex("^([a-zA-Z_0-9]+)[\u00A0 ]((?:<[-–]>)|(?:=>)) \\([a-zA-Z<>\\*+$]+\\)$", QRegularExpression::CaseInsensitiveOption); + static const QRegularExpression topicPropertyRegex("^([a-zA-Z_0-9<>-]+)[\u00A0 ]((?:<[-–]>)|(?:=>)) (\\([a-zA-Z<>\\*+$]+\\))$", QRegularExpression::CaseInsensitiveOption); if (!topicHeaderRegex.isValid() || !topicGenericItemRegex.isValid() || !topicFunctionRegex.isValid() || !topicMethodRegex.isValid() || !topicPropertyRegex.isValid()) qDebug() << "QtSLiMHelpWindow::addTopicsFromRTFFile(): invalid regex"; @@ -849,51 +849,104 @@ void QtSLiMHelpWindow::addTopicsFromRTFFile(const QString &htmlFile, // This topic item is a method declaration QString callName = match_topicPropertyRegex.captured(1); QString readOnlyName = match_topicPropertyRegex.captured(2); + QString resultName = match_topicPropertyRegex.captured(3); //qDebug() << "topic property name: " << callName << ", line: " << line; - // Check for a built-in property signature that matches and substitute it in. Note that we accept a match from any property in any class - // API as long as the signature matches; we do not rigorously check that the API within a given class matches between signature and doc. - // This is mostly not a problem because it is quite rare for the same property name to be used with more than one signature. - if (propertyList) + if (callName.startsWith('<')) { + // BCH 1/7/2026: This case is hit for dynamic properties, which are present in the doc with names like Effect. We don't want to + // check for a match, we just want to accept the property as it is. We do want to reformat it, though; we make a temporary signature for that. std::string property_name(callName.toStdString()); - bool found_match = false, found_mismatch = false; - QString oldSignatureString, newSignatureString; + bool property_read_only = true; - for (const auto &signature_iter : *propertyList) - if (signature_iter->property_name_.compare(property_name) == 0) - { - const EidosPropertySignature *property_signature = signature_iter.get(); - - oldSignatureString = lineCursor.selectedText(); - std::ostringstream ss; - ss << *property_signature; - newSignatureString = QString::fromStdString(ss.str()); - - if (newSignatureString == oldSignatureString) + if (readOnlyName == "=>") + property_read_only = true; + else if ((readOnlyName == "<->") || (readOnlyName == "<–>")) + property_read_only = false; + else + qDebug() << "*** unrecognized readOnlyName " << readOnlyName << " parsing " << callName; + + EidosValueMask property_mask = kEidosValueMaskInt | kEidosValueMaskSingleton; + EidosClass *property_class_name = nullptr; + + if (resultName == "(float$)") + property_mask = kEidosValueMaskFloat | kEidosValueMaskSingleton; + else if (resultName == "(object$)") + { + property_mask = kEidosValueMaskObject | kEidosValueMaskSingleton; + property_class_name = gSLiM_Trait_Class; + } + else + qDebug() << "*** unrecognized resultName " << resultName << " parsing " << callName; + + EidosPropertySignature_CSP dynamic_signature; + + if (property_class_name) + dynamic_signature = EidosPropertySignature_CSP(new EidosPropertySignature(property_name, property_read_only, property_mask, property_class_name)); + else + dynamic_signature = EidosPropertySignature_CSP(new EidosPropertySignature(property_name, property_read_only, property_mask)); + + QString oldSignatureString = lineCursor.selectedText(); + std::ostringstream ss; + ss << *dynamic_signature; + QString dynamicSignatureString = QString::fromStdString(ss.str()); + + if (dynamicSignatureString == oldSignatureString) + { + // Replace the signature line with the syntax-colored version + ColorizePropertySignature(dynamic_signature.get(), 11.0, lineCursor); + } + else + { + qDebug() << "*** signature mismatch for dynamic property " << callName; + } + } + else + { + // Check for a built-in property signature that matches and substitute it in. Note that we accept a match from any property in any class + // API as long as the signature matches; we do not rigorously check that the API within a given class matches between signature and doc. + // This is mostly not a problem because it is quite rare for the same property name to be used with more than one signature. + if (propertyList) + { + std::string property_name(callName.toStdString()); + bool found_match = false, found_mismatch = false; + QString oldSignatureString, newSignatureString; + + for (const auto &signature_iter : *propertyList) + if (signature_iter->property_name_.compare(property_name) == 0) { - //qDebug() << "signature match for method" << callName; + const EidosPropertySignature *property_signature = signature_iter.get(); - // Replace the signature line with the syntax-colored version - ColorizePropertySignature(property_signature, 11.0, lineCursor); - found_match = true; - break; - } - else - { - // If we find a mismatched signature but no matching signature, that's probably an error in either the doc or - // the signature, unless we find a match later on with a different signature for the same property name. - found_mismatch = true; + oldSignatureString = lineCursor.selectedText(); + std::ostringstream ss; + ss << *property_signature; + newSignatureString = QString::fromStdString(ss.str()); + + if (newSignatureString == oldSignatureString) + { + //qDebug() << "signature match for method" << callName; + + // Replace the signature line with the syntax-colored version + ColorizePropertySignature(property_signature, 11.0, lineCursor); + found_match = true; + break; + } + else + { + // If we find a mismatched signature but no matching signature, that's probably an error in either the doc or + // the signature, unless we find a match later on with a different signature for the same property name. + found_mismatch = true; + } } - } - - if (found_mismatch && !found_match) - qDebug() << "*** property signature mismatch:\nold: " << oldSignatureString << "\nnew: " << newSignatureString; - else if (!found_match) - qDebug() << "*** no property signature found for property name" << callName; - } - + + if (found_mismatch && !found_match) + qDebug() << "*** property signature mismatch:\nold: " << oldSignatureString << "\nnew: " << newSignatureString; + else if (!found_match) + qDebug() << "*** no property signature found for property name" << callName; + } + } + topicItemKey = callName + "\u00A0" + readOnlyName; topicItemCursor = new QTextCursor(lineCursor); } @@ -967,7 +1020,11 @@ void QtSLiMHelpWindow::checkDocumentationOfClass(EidosClass *classObject) qDebug() << "*** no documentation found for class " << className << " property " << property_string; } } - + + // BCH 1/7/2026: Remove dynamic properties like "Effect" from the excess documentation list; they are not expected to have a match + static const QRegularExpression nonDynamicProperties("^[^<]"); + docProperties = docProperties.filter(nonDynamicProperties); + if (docProperties.size()) qDebug() << "*** excess documentation found for class " << className << " properties " << docProperties; } diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index b77f6586..b6896359 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -378,6 +378,8 @@

Sets a new nucleotide mutation matrix for the genomic element type.  This replaces the mutation matrix originally set by initializeGenomicElementType().  This method may only be called in nucleotide-based models.

5.7  Class Individual

5.7.1  Individual properties

+

<trait-name> <–> (float$)

+

This dynamic property provides the phenotype of the individual for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The phenotypeForTrait() method provides an alternative way to access phenotype values.

age <–> (integer$)

The age of the individual, measured in cycles.  A newly generated offspring individual will have an age of 0 in the same tick in which it was created.  The age of every individual is incremented by one at the same point that its species cycle counter is incremented, at the end of the tick cycle, if and only if its species was active in that tick.  The age of individuals may be changed; usually this only makes sense when setting up the initial state of a model, however.

cachedFitness => (float$)

@@ -477,8 +479,6 @@

This method replaces the deprecated method uniqueMutationsOfType(), while providing additional useful options.  It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.  See also the method zygosityOfMutations(), which provides an alternative approach for assessing the zygosity of mutations in an individual.

– (float)offsetForTrait([Niso<Trait> trait = NULL])

Returns the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

-

– (float)phenotypeForTrait([Niso<Trait> trait = NULL])

-

Returns the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+ (void)outputIndividuals([Ns$ filePath = NULL], [logical$ append = F], [Niso<Chromosome>$ chromosome = NULL], [logical$ spatialPositions = T], [logical$ ages = T], [logical$ ancestralNucleotides = F], [logical$ pedigreeIDs = F], [logical$ objectTags = F])

Output the state of the target vector of individuals in SLiM's own format.  If the optional parameter filePath is NULL (the default), output will be sent to Eidos’s output stream.  Otherwise, output will be sent to the filesystem path specified by filePath, overwriting that file if append if F, or appending to the end of it if append is T.  This method is quite similar to the Species method outputFull(), but (1) it can produce output for any vector of individuals, not always for the entire population; (2) it does not support output in a binary format; (3) it can produce output regarding the genetics for all chromosomes or for just one focal chromosome; and (4) there is no corresponding read method, as readFromPopulationFile() can read the data saved by outputFull().

The chromosome parameter specifies a focal chromosome for which the genetics of the target individuals will be output.  If chromosome is NULL, all chromosomes will be output; otherwise, chromosome may specify the focal chromosome with an integer chromosome id, a string chromosome symbol, or a Chromosome object.

@@ -493,6 +493,8 @@

The chromosome parameter specifies a focal chromosome for which the genetics of the target individuals will be output.  If chromosome is NULL, all chromosomes will be output (distinguished in the VCF output by the chromosome symbol output in the CHROM column); otherwise, chromosome may specify the focal chromosome with an integer chromosome id, a string chromosome symbol, or a Chromosome object.

The parameters outputMultiallelics, simplifyNucleotides, and outputNonnucleotides affect the format of the output produced.

Output is generally done in a late() event, so that the output reflects the state of the simulation at the end of a tick.

+

– (float)phenotypeForTrait([Niso<Trait> trait = NULL])

+

Returns the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+ (object<Mutation>)readIndividualsFromVCF(string$ filePath, [Nio<MutationType>$ mutationType = NULL])

Read new mutations from the VCF format file at filePath and add them to the target individuals.  The number of target individuals must match the number of samples represented in the VCF file; each sample will be associated with a corresponding target individual, in the order that the samples and the target individuals are provided.  To read into all of the individuals in a given subpopulation pN, simply call pN.individuals.readIndividualsFromVCF(), assuming the subpopulation’s size matches the number of samples in the VCF file.  A vector containing all of the mutations created by readIndividualsFromVCF() is returned (not necessarily in the order of the corresponding VCF call lines).

This method and the readHaplosomesFromVCF() method of Haplosome provide two alternative ways of reading VCF data, focused on the perspective of either individuals (this method) or haplosomes (the Haplosome method).  As described above, this method draws a correspondence between VCF samples and individuals, whereas the Haplosome method draws a correspondence between VCF calls and haplosomes.  For example, if a VCF call line contained a series of calls like “1|1 0|1 1 0 1|0” this method would see that as calls for five individuals, three of which are diploid for the chromosome being called, and two of which are haploid.  That would make sense if, for example, the chromosome being called is an X chromosome; the diploid individuals would be females, the haploid individuals would be males.  The vector of target individuals would need to contain two females, then two males, and then a female, so that the structure of the calls matched the haplosome structure of the individuals, or an error would result.  The readHaplosomesFromVCF() method of Haplosome, on the other hand, would see that same series of calls as corresponding to eight haplosomes, and would assign each call to the corresponding non-null target haplosome, without regard to whether the VCF’s grouping into diploid and haploid calls corresponded to any coherent structure of individuals in the SLiM model.  Each approach has advantages and disadvantages.  This method provides much more error-checking and safety when your intention is to read individual-level data from VCF; it can check that the ploidy in the VCF data matches the ploidy of each target individual, and that diploid calls like 1|1 get assigned to the two haplosomes of a single individual correctly.  The readHaplosomesFromVCF() method of Haplosome does not perform such checks, and can push VCF data into any arbitrary set of haplosomes, so it provides more power and flexibility, but less error-checking and less intelligence.

@@ -690,6 +692,12 @@

Returns T if the log file is configured to log a new row automatically at the end of the current tick; otherwise, returns F.  This is useful for calculating a value that will be logged only in ticks when the value is needed.

5.10  Class Mutation

5.10.1  Mutation properties

+

<trait-name>Dominance <–> (float$)

+

This dynamic property provides the dominance coefficient of the mutation for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The dominance property and the dominanceForTrait() method provide alternative ways to access dominance values.

+

<trait-name>Effect <–> (float$)

+

This dynamic property provides the effect size of the mutation for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The effect property and the effectForTrait() method provide alternative ways to access effect values.

+

<trait-name>HemizygousDominance <–> (float$)

+

This dynamic property provides the hemizygous dominance coefficient of the mutation for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The hemizygousDominance property and the hemizygousDominanceForTrait() method provide alternative ways to access hemizygous dominance values.

chromosome => (object<Chromosome>$)

The Chromosome object with which the mutation is associated.

dominance => (float)

@@ -978,6 +986,8 @@

Subtracts x from the spatial map.  One possibility is that x is a singleton integer or float value; in this case, x is subtracted from each grid value of the target spatial map.  Another possibility is that x is an integer or float vector/matrix/array of the same dimensions as the target spatial map’s grid; in this case, each value of x is subtracted from the corresponding grid value of the target spatial map.  The third possibility is that x is itself a (singleton) spatial map; in this case, each grid value of x is subtracted from the corresponding grid value of the target spatial map (and thus the two spatial maps must match in their spatiality, their spatial bounds, and their grid dimensions).  The target spatial map is returned, to allow easy chaining of operations.

5.16  Class Species

5.16.1  Species properties

+

<trait-name> => (object<Trait>$)

+

This dynamic property provides the Trait object in the species for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The traits property and the traitsWithIndices() and traitsWithNames() methods provide alternative ways to access Trait objects.

avatar => (string$)

The avatar string used to represent this species in SLiMgui.  Outside of SLiMgui, this property still exists, but is not used by SLiM.  Avatars are typically one-character strings, often using an emoji that symbolizes the species.  This property is read-only; its value should be set with the avatar parameter of initializeSpecies().

chromosome => (object<Chromosome>$)

@@ -1371,6 +1381,12 @@

Note that this method is only for use in nonWF models, in which migration is managed manually by the model script.  In WF models, migration is managed automatically by the SLiM core based upon the migration rates set for each subpopulation with setMigrationRates().

5.18  Class Substitution

5.18.1  Substitution properties

+

<trait-name>Dominance => (float$)

+

This dynamic property provides the dominance coefficient of the substitution for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The dominance property and the dominanceForTrait() method provide alternative ways to access dominance values.

+

<trait-name>Effect => (float$)

+

This dynamic property provides the effect size of the substitution for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The effect property and the effectForTrait() method provide alternative ways to access effect values.

+

<trait-name>HemizygousDominance => (float$)

+

This dynamic property provides the hemizygous dominance coefficient of the substitution for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The hemizygousDominance property and the hemizygousDominanceForTrait() method provide alternative ways to access hemizygous dominance values.

chromosome => (object<Chromosome>$)

The Chromosome object with which the substitution is associated.

dominance => (float)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 883c3440..a19e9a52 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -2926,7 +2926,18 @@ The species to which the target object belongs.\ \f1\fs22 properties\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf0 age \cf2 \expnd0\expndtw0\kerning0 +\f2\fs18 \cf2 +\f3\i0 <\'96> (float$)\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 This dynamic property provides the phenotype of the individual for the trait named +\f2\i\fs18 +\f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The +\f3\fs18 phenotypeForTrait() +\f4\fs20 method provides an alternative way to access phenotype values.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf0 age \cf2 \expnd0\expndtw0\kerning0 <\'96>\cf0 \kerning1\expnd0\expndtw0 (integer$)\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 @@ -3896,24 +3907,6 @@ This method replaces the deprecated method \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)phenotypeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ -\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf2 Returns the individual phenotype(s) for the trait(s) specified by -\f3\fs18 trait -\f4\fs20 . The traits can be specified as -\f3\fs18 integer -\f4\fs20 indices or -\f3\fs18 string -\f4\fs20 names of traits in the species, or directly as -\f3\fs18 Trait -\f4\fs20 objects; -\f3\fs18 NULL -\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by -\f3\fs18 trait -\f4\fs20 .\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - \f3\fs18 \cf2 +\'a0(void)outputIndividuals([Ns$\'a0filePath\'a0=\'a0NULL], [logical$\'a0append\'a0=\'a0F], [Niso$\'a0chromosome\'a0=\'a0NULL], [logical$\'a0spatialPositions\'a0=\'a0T], [logical$\'a0ages\'a0=\'a0T], [logical$\'a0ancestralNucleotides\'a0=\'a0F], [logical$\'a0pedigreeIDs\'a0=\'a0F], [logical$\'a0objectTags\'a0=\'a0F])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 @@ -4108,6 +4101,24 @@ Output is generally done in a \f4\fs20 event, so that the output reflects the state of the simulation at the end of a tick.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(float)phenotypeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the individual phenotype(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 +\'a0(object)readIndividualsFromVCF(string$\'a0filePath, [Nio$\'a0mutationType\'a0=\'a0NULL])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 @@ -6142,7 +6153,49 @@ You can get the \f1\fs22 properties\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 chromosome => (object$)\ +\f2\fs18 \cf2 +\f3\i0 Dominance \expnd0\expndtw0\kerning0 +<\'96>\kerning1\expnd0\expndtw0 (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 This dynamic property provides the dominance coefficient of the mutation for the trait named +\f2\i\fs18 +\f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The +\f3\fs18 dominance +\f4\fs20 property and the +\f3\fs18 dominanceForTrait() +\f4\fs20 method provide alternative ways to access dominance values.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f2\i\fs18 \cf2 +\f3\i0 Effect \expnd0\expndtw0\kerning0 +<\'96>\kerning1\expnd0\expndtw0 (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 This dynamic property provides the effect size of the mutation for the trait named +\f2\i\fs18 +\f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The +\f3\fs18 effect +\f4\fs20 property and the +\f3\fs18 effectForTrait() +\f4\fs20 method provide alternative ways to access effect values.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f2\i\fs18 \cf2 +\f3\i0 HemizygousDominance \expnd0\expndtw0\kerning0 +<\'96>\kerning1\expnd0\expndtw0 (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 This dynamic property provides the hemizygous dominance coefficient of the mutation for the trait named +\f2\i\fs18 +\f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The +\f3\fs18 hemizygousDominance +\f4\fs20 property and the +\f3\fs18 hemizygousDominanceForTrait() +\f4\fs20 method provide alternative ways to access hemizygous dominance values.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 chromosome => (object$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 The @@ -9148,7 +9201,26 @@ The density scale of the kernel has no effect and will be normalized; this is th \f1\fs22 properties\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 avatar => (string$)\ +\f2\fs18 \cf2 +\f3\i0 => (object$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 This dynamic property provides the +\f3\fs18 Trait +\f4\fs20 object in the species for the trait named +\f2\i\fs18 +\f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The +\f3\fs18 traits +\f4\fs20 property and the +\f3\fs18 traitsWithIndices() +\f4\fs20 and +\f3\fs18 traitsWithNames() +\f4\fs20 methods provide alternative ways to access +\f3\fs18 Trait +\f4\fs20 objects.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 avatar => (string$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 The avatar string used to represent this species in SLiMgui. Outside of SLiMgui, this property still exists, but is not used by SLiM. Avatars are typically one-character strings, often using an emoji that symbolizes the species. This property is read-only; its value should be set with the @@ -14312,7 +14384,46 @@ Note that this method is only for use in nonWF models, in which migration is man \f1\fs22 properties\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 chromosome => (object$)\ +\f2\fs18 \cf2 +\f3\i0 Dominance => (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 This dynamic property provides the dominance coefficient of the substitution for the trait named +\f2\i\fs18 +\f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The +\f3\fs18 dominance +\f4\fs20 property and the +\f3\fs18 dominanceForTrait() +\f4\fs20 method provide alternative ways to access dominance values.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f2\i\fs18 \cf2 +\f3\i0 Effect => (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 This dynamic property provides the effect size of the substitution for the trait named +\f2\i\fs18 +\f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The +\f3\fs18 effect +\f4\fs20 property and the +\f3\fs18 effectForTrait() +\f4\fs20 method provide alternative ways to access effect values.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f2\i\fs18 \cf2 +\f3\i0 HemizygousDominance => (float$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 This dynamic property provides the hemizygous dominance coefficient of the substitution for the trait named +\f2\i\fs18 +\f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The +\f3\fs18 hemizygousDominance +\f4\fs20 property and the +\f3\fs18 hemizygousDominanceForTrait() +\f4\fs20 method provide alternative ways to access hemizygous dominance values.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 chromosome => (object$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 The diff --git a/VERSIONS b/VERSIONS index e8731671..f7acc781 100644 --- a/VERSIONS +++ b/VERSIONS @@ -134,6 +134,7 @@ multitrait branch: extend the Eidos deleteFile() and fileExists() functions to support passing in a vector of file paths extend the Eidos + operator to support adding logical+logical, integer+logical, and logical+integer (all for NxN, 1xN, Nx1), as in R add zygosityOfMutations([No mutations = NULL], [integer$ hemizygousValue = 1], [integer$ haploidValue = 1]) for assessing zygosity (see also mutationsFromHaplosomes()) + add SLiMgui doc for the various dynamic properties added for traits version 5.1 (Eidos version 4.1): diff --git a/core/mutation.cpp b/core/mutation.cpp index 5f2cd875..f3683c94 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -1168,7 +1168,8 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & default: { - // Here we implement a special behavior: you can do mutation.Effect and mutation.Dominance to access a trait's values directly. + // Here we implement a special behavior: you can do mutation.Effect, mutation.Dominance, + // and mutation.HemizygousDominance to access a trait's values directly. // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = mutation_type_ptr_->species_; From 9e6d38d1271194da4f9dd3839aa1cb2d271d85c5 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 8 Jan 2026 20:50:50 -0600 Subject: [PATCH 072/107] make separate per-individual and per-subpop code paths for phenotype demand --- QtSLiM/help/SLiMHelpClasses.html | 18 +- SLiMgui/SLiMHelpClasses.rtf | 66 +++++- VERSIONS | 1 + core/chromosome.cpp | 2 +- core/community.cpp | 24 ++- core/community.h | 2 +- core/community_eidos.cpp | 4 +- core/individual.cpp | 360 ++++++++++++++++++++++++++++--- core/individual.h | 9 +- core/interaction_type.cpp | 2 +- core/population.cpp | 20 +- core/slim_eidos_block.cpp | 2 +- core/slim_globals.cpp | 3 +- core/slim_globals.h | 6 +- core/species.cpp | 24 +-- core/species.h | 3 +- core/species_eidos.cpp | 101 ++++++++- core/subpopulation.cpp | 133 ++++++------ core/subpopulation.h | 17 +- 19 files changed, 630 insertions(+), 167 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index b6896359..979fe1f6 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -463,10 +463,10 @@

Returns a logical vector indicating whether each of the mutations in mutations is present in the individual (in any of its haplosomes); each element in the returned vector indicates whether the corresponding mutation is present (T) or absent (F).  This method is provided for speed; it is much faster than the corresponding Eidos code.

 (integer$)countOfMutationsOfType(io<MutationType>$ mutType)

Returns the number of mutations that are of the type specified by mutType, out of all of the mutations in the individual (in all of its haplosomes; a mutation that is present in both homologous haplosomes counts twice).  If you need a vector of the matching Mutation objects, rather than just a count, you should probably use mutationsFromHaplosomes().  This method is provided for speed; it is much faster than the corresponding Eidos code.

-

+ (void)demandPhenotype([Niso<Trait> trait = NULL], [logical$ forceRecalc = F])

-

Expresses “demand” for the phenotypes (trait values) of the trait(s) specified by trait, for the target vector of individuals.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  This triggers the recalculation of those trait values in those individuals, as needed.  More specifically, if forceRecalc is F (the default), the specified trait values will only be recalculated if their current value is NAN; if they have any other value, they are considered to already be calculated, and will not be recalculated.  If forceRecalc is T, the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date.  No value is returned by demandPhenotype(), since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.

-

This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set.  Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual.  The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.

-

Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so demandPhenotype() does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.

+

+ (void)demandPhenotypeForIndividuals([Niso<Trait> trait = NULL], [logical$ forceRecalc = F])

+

Expresses “demand” for the phenotypes (trait values) of the trait(s) specified by trait, for the target vector of individuals.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  This triggers the recalculation of those trait values in those individuals, as needed.  More specifically, if forceRecalc is F (the default), the specified trait values will only be recalculated if their current value is NAN; if they have any other value, they are considered to already be calculated, and will not be recalculated.  If forceRecalc is T, the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date.  No value is returned by demandPhenotypeForIndividuals(), since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.  Note that the target individuals are processed in the order in which they are provided; ordering artifacts could be observed if mutationEffect() callbacks with order-dependency effects are active.

+

This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set.  Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual.  The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.  Note that the Species method demandPhenotype() is more efficient than this individual-level method since it processes the whole species at once, allowing a more structured computational approach.

+

Note that the fitness recalculation tick cycle stage automatically expresses demand for all traits that have a direct effect on fitness, so phenotypes often do not need to be demanded explicitly; doing so is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.

– (object<Haplosome>)haplosomesForChromosomes([Niso<Chromosome> chromosomes = NULL], [Ni$ index = NULL], [logical$ includeNulls = T])

Returns a vector containing the haplosomes of the target individual that correspond to the chromosomes passed in chromosomes (following the order of the chromosomes property of Individual).  Chromosomes can be specified by id (integer), by symbol (string) or by the Chromosome objects themselves; if NULL is passed (the default), all chromosomes defined for the species are used, in the order in which they were defined.

For chromosomes that are intrinsically diploid (types "A", "X", and "Z", as well as the "H-" and "-Y" backward-compatibility chromosome types), index can be 0 or 1, requesting only the first or second haplosome, respectively, for that chromosome; for other chromosome types, index is ignored.  If includeNulls is T (the default), any null haplosomes corresponding to the specified chromosomes are included in the result; if it is F, null haplosomes are excluded.  See also the properties haplosomes, haplosomesNonNull, haploidGenome1, haploidGenome1NonNull, haploidGenome2, and haploidGenome2NonNull.

@@ -742,11 +742,11 @@

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

-

Returns whether the mutation is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A mutation is configured for independent dominance for a trait if it inherited a default dominance of NAN for the trait from its mutation type, or if its dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation in section 26.19 for discussion of this feature.  Independent dominance flags for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

Returns whether the mutation is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A mutation is configured for independent dominance for a trait if it inherited a default dominance of NAN for the trait from its mutation type, or if its dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation for discussion of this feature.  Independent dominance flags for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+ (void)setDominanceForTrait([Niso<Trait> trait = NULL], [Nif dominance = NULL])

Sets the target mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.)  In the second pattern, dominance is a singleton value; this sets the given dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the dominance for each of the specified traits in one mutation before moving to the next mutation.

-

A dominance value of NAN for a given trait configures the mutation to use independent dominance for that trait; see the class Trait documentation in section 26.19 for discussion of this feature, which can also be set at the MutationType level with initializeMutationType() or setDefaultDominanceForTrait().  A given mutation may be configured to exhibit independent dominance for some traits and not for others; a mixed configuration is allowed.

+

A dominance value of NAN for a given trait configures the mutation to use independent dominance for that trait; see the class Trait documentation for discussion of this feature, which can also be set at the MutationType level with initializeMutationType() or setDefaultDominanceForTrait().  A given mutation may be configured to exhibit independent dominance for some traits and not for others; a mixed configuration is allowed.

+ (void)setEffectForTrait([Niso<Trait> trait = NULL], [Nif effect = NULL])

Sets the mutation’s effect(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter effect must follow one of four patterns.  In the first pattern, effect is NULL; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation.  (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.)  In the second pattern, effect is a singleton value; this sets the given effect for each of the specified traits in each target mutation.  In the third pattern, effect is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation.  In the fourth pattern, effect is of length equal to the number of specified traits times the number of target mutations; this uses effect to provide a different effect value for each trait in each mutation, using consecutive values from effect to set the effect for each of the specified traits in one mutation before moving to the next mutation.

@@ -1067,6 +1067,10 @@

Returns a vector of Chromosome objects corresponding to the chromosome symbols supplied in symbols, in the same order.  If any chromosome symbol in symbols does not correspond to a chromosome in the target species, an error will be raised.  See also chromosomesOfType() and chromosomesWithIDs().

– (integer$)countOfMutationsOfType(io<MutationType>$ mutType)

Returns the number of mutations that are of the type specified by mutType, out of all of the mutations that are currently active in the species.  If you need a vector of the matching Mutation objects, rather than just a count, use -mutationsOfType().  This method is often used to determine whether an introduced mutation is still active (as opposed to being either lost or fixed).  This method is provided for speed; it is much faster than the corresponding Eidos code.

+

– (void)demandPhenotype(Nio<Subpopulation> subpops, [Niso<Trait> trait = NULL], [logical$ forceRecalc = F])

+

Expresses “demand” for the phenotypes (trait values) of the trait(s) specified by trait, for all individuals in the subpopulations specified by subpops.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects; NULL represents all of the subpopulations in the species.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  This triggers the recalculation of those trait values in those subpopulations, as needed.  More specifically, if forceRecalc is F (the default), the specified trait values will only be recalculated if their current value is NAN; if they have any other value, they are considered to already be calculated, and will not be recalculated.  If forceRecalc is T, the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date.  No value is returned by demandPhenotype(), since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.

+

This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set.  Similarly, it is much better to call it for all subpopulations than to call it separately for each subpopulation.  The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.  Note that the Individual method demandPhenotypeForIndividuals() allows demand to be expressed for traits in a specific set of individuals, but is significantly less efficient than this method.

+

Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so demandPhenotype() does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.

– (object<Individual>)individualsWithPedigreeIDs(integer pedigreeIDs, [Nio<Subpopulation> subpops = NULL])

Looks up individuals by pedigree ID, optionally within specific subpopulations.  Pedigree tracking must be turned on with initializeSLiMOptions(keepPedigrees=T) to use this method, otherwise an error will result.  This method is vectorized; more than one pedigree id may be passed in pedigreeID, in which case the returned vector will contain all of the individuals for which a match was found (in the same order in which they were supplied).  If a given id is not found, the returned vector will contain no entry for that id (so the length of the returned vector may not match the length of pedigreeIDs).  If none of the given ids were found, the returned vector will be object<Individual>(0), an empty object vector of class Individual.  If you have more than one pedigree ID to look up, calling this method just once, in vectorized fashion, may be much faster than calling it once for each ID, due to internal optimizations.

To find individuals within all subpopulations, pass the default of NULL for subpops.  If you are interested only in matches within a specific subpopulation, pass that subpopulation for subpops; that will make the search faster.  Similarly, if you know that a particular subpopulation is the most likely to contain matches, you should supply that subpopulation first in the subpops vector so that it will be searched first; the supplied subpopulations are searched in order.  Subpopulations may be supplied either as integer IDs, or as Subpopulation objects.

@@ -1423,7 +1427,7 @@

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

Returns the substitution’s hemizygous dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

-

Returns whether the substitution is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A substitution is configured for independent dominance for a trait if the original mutation inherited a default dominance of NAN for the trait from its mutation type, or if the original mutation’s dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation in section 26.19 for discussion of this feature.  Independent dominance flags for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

Returns whether the substitution is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A substitution is configured for independent dominance for a trait if the original mutation inherited a default dominance of NAN for the trait from its mutation type, or if the original mutation’s dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation for discussion of this feature.  Independent dominance flags for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

5.19  Class Trait

5.19.1  Trait properties

baselineOffset <–> (float$)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index a19e9a52..bd638fdf 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -3703,7 +3703,7 @@ See the description of the \f4\fs20 . This method is provided for speed; it is much faster than the corresponding Eidos code.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 +\'a0(void)demandPhenotype([Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0forceRecalc\'a0=\'a0F])\ +\f3\fs18 \cf2 +\'a0(void)demandPhenotypeForIndividuals([Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0forceRecalc\'a0=\'a0F])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Expresses \'93demand\'94 for the phenotypes (trait values) of the trait(s) specified by @@ -3727,12 +3727,16 @@ See the description of the \f4\fs20 is \f3\fs18 T \f4\fs20 , the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date. No value is returned by +\f3\fs18 demandPhenotypeForIndividuals() +\f4\fs20 , since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards. Note that the target individuals are processed in the order in which they are provided; ordering artifacts could be observed if +\f3\fs18 mutationEffect() +\f4\fs20 callbacks with order-dependency effects are active.\ +This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set. Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual. The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process. Note that the +\f3\fs18 Species +\f4\fs20 method \f3\fs18 demandPhenotype() -\f4\fs20 , since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.\ -This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set. Similarly, it is much better to call it once on a whole vector of individuals than to call it separately for each individual. The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process.\ -Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so -\f3\fs18 demandPhenotype() -\f4\fs20 does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.\ +\f4\fs20 is more efficient than this individual-level method since it processes the whole species at once, allowing a more structured computational approach.\ +Note that the fitness recalculation tick cycle stage automatically expresses demand for all traits that have a direct effect on fitness, so phenotypes often do not need to be demanded explicitly; doing so is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(object)haplosomesForChromosomes([Niso\'a0chromosomes\'a0=\'a0NULL], [Ni$\'a0index\'a0=\'a0NULL], [logical$\'a0includeNulls\'a0=\'a0T])\ @@ -6532,7 +6536,7 @@ If the target mutation has been configured to exhibit independent dominance for \f3\fs18 setDominanceForTrait() \f4\fs20 ; see the class \f3\fs18 Trait -\f4\fs20 documentation in section 26.19 for discussion of this feature. Independent dominance flags for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f4\fs20 documentation for discussion of this feature. Independent dominance flags for a given target mutation will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -6572,7 +6576,7 @@ A dominance value of \f3\fs18 NAN \f4\fs20 for a given trait configures the mutation to use independent dominance for that trait; see the class \f3\fs18 Trait -\f4\fs20 documentation in section 26.19 for discussion of this feature, which can also be set at the +\f4\fs20 documentation for discussion of this feature, which can also be set at the \f3\fs18 MutationType \f4\fs20 level with \f3\fs18 initializeMutationType() @@ -10074,6 +10078,50 @@ Subpopulations added by this method will consist of individuals that are clonal \f5\fs20 . \f4 This method is often used to determine whether an introduced mutation is still active (as opposed to being either lost or fixed). This method is provided for speed; it is much faster than the corresponding Eidos code. \f5 \ +\pard\pardeftab529\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(void)demandPhenotype(Nio\'a0subpops, [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0forceRecalc\'a0=\'a0F])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Expresses \'93demand\'94 for the phenotypes (trait values) of the trait(s) specified by +\f3\fs18 trait +\f4\fs20 , for all individuals in the subpopulations specified by +\f3\fs18 subpops +\f4\fs20 . Subpopulations may be supplied either as +\f3\fs18 integer +\f4\fs20 IDs, or as +\f3\fs18 Subpopulation +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the subpopulations in the species. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. This triggers the recalculation of those trait values in those subpopulations, as needed. More specifically, if +\f3\fs18 forceRecalc +\f4\fs20 is +\f3\fs18 F +\f4\fs20 (the default), the specified trait values will only be recalculated if their current value is +\f3\fs18 NAN +\f4\fs20 ; if they have any other value, they are considered to already be calculated, and will not be recalculated. If +\f3\fs18 forceRecalc +\f4\fs20 is +\f3\fs18 T +\f4\fs20 , the specified trait values will be recalculated regardless of their current value, which can be useful if they are known to be out of date. No value is returned by +\f3\fs18 demandPhenotype() +\f4\fs20 , since assembling that return value would require additional work that would sometimes be wasted; instead, if you want to obtain the new trait values you should fetch them separately afterwards.\ +This method entails significant per-call overhead, so it is much better to call it once for a whole set of traits than to call it separately for each trait in that set. Similarly, it is much better to call it for all subpopulations than to call it separately for each subpopulation. The more work you give this method to do in a single call, the more it will be able to share work and optimize the calculation process. Note that the +\f3\fs18 Individual +\f4\fs20 method +\f3\fs18 demandPhenotypeForIndividuals() +\f4\fs20 allows demand to be expressed for traits in a specific set of individuals, but is significantly less efficient than this method.\ +Note that the fitness recalculation tick cycle stage intrinsically expresses demand for all traits that have a direct effect on fitness, so +\f3\fs18 demandPhenotype() +\f4\fs20 does not usually need to be called explicitly; it is useful primarily for expressing demand for traits that are not directly connected to fitness, but are used in some other way.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(object)individualsWithPedigreeIDs(integer\'a0pedigreeIDs, [Nio\'a0subpops\'a0=\'a0NULL])\ @@ -14669,7 +14717,7 @@ Note that this method is only for use in nonWF models, in which migration is man \f3\fs18 setDominanceForTrait() \f4\fs20 ; see the class \f3\fs18 Trait -\f4\fs20 documentation in section 26.19 for discussion of this feature. Independent dominance flags for a given target substitution will be returned consecutively in the order in which the traits are specified by +\f4\fs20 documentation for discussion of this feature. Independent dominance flags for a given target substitution will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait \f4\fs20 .\ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 diff --git a/VERSIONS b/VERSIONS index f7acc781..e67bbe98 100644 --- a/VERSIONS +++ b/VERSIONS @@ -135,6 +135,7 @@ multitrait branch: extend the Eidos + operator to support adding logical+logical, integer+logical, and logical+integer (all for NxN, 1xN, Nx1), as in R add zygosityOfMutations([No mutations = NULL], [integer$ hemizygousValue = 1], [integer$ haploidValue = 1]) for assessing zygosity (see also mutationsFromHaplosomes()) add SLiMgui doc for the various dynamic properties added for traits + add a Species demandPhenotype() method, and change the Individual method demandPhenotype() to demandPhenotypeForIndividuals(); the Species method is generally preferred for speed version 5.1 (Eidos version 4.1): diff --git a/core/chromosome.cpp b/core/chromosome.cpp index 6241a77e..adfd2c11 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1773,7 +1773,7 @@ void Chromosome::DrawBreakpoints(Individual *p_parent, Haplosome *p_haplosome1, { parent_sex = p_parent->sex_; parent_subpop = p_parent->subpopulation_; - recombination_callbacks = species_.CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, parent_subpop->subpopulation_id_, -1, id_); + recombination_callbacks = species_.CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, parent_subpop->subpopulation_id_, -1, id_, /* p_active_only */ false); // SPECIES CONSISTENCY CHECK if (&p_parent->subpopulation_->species_ != &species_) diff --git a/core/community.cpp b/core/community.cpp index 2673cd1b..f2ddd260 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -556,7 +556,7 @@ void Community::ValidateScriptBlockCaches(void) } } -std::vector Community::ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id, Species *p_species) +std::vector Community::ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id, Species *p_species, bool p_active_only) { if (!script_block_types_cached_) ValidateScriptBlockCaches(); @@ -660,6 +660,12 @@ std::vector Community::ScriptBlocksMatching(slim_tick_t p_tick, if (p_species != script_block->species_spec_) continue; + // check that the block is active, if the caller has requested only active script blocks; in general this + // is not a good idea, since a callback can change its own active flag or that of another callback unless + // there is a specific mechanism preventing that; see SetInsideTraitOrFitnessCalculation() for example + if (p_active_only && !script_block->block_active_) + continue; + // OK, everything matches, so we want to return this script block matches.emplace_back(script_block); } @@ -2291,7 +2297,7 @@ void Community::AllSpecies_RunInitializeCallbacks(void) // The zero tick is handled here by shared code, since it is the same for WF and nonWF models // execute user-defined function blocks first; no need to profile this, it's just the definitions not the executions - std::vector function_blocks = ScriptBlocksMatching(-1, SLiMEidosBlockType::SLiMEidosUserDefinedFunction, -1, -1, -1, -1, -1, nullptr); + std::vector function_blocks = ScriptBlocksMatching(-1, SLiMEidosBlockType::SLiMEidosUserDefinedFunction, -1, -1, -1, -1, -1, nullptr, /* p_active_only */ false); for (auto script_block : function_blocks) ExecuteFunctionDefinitionBlock(script_block); @@ -2391,7 +2397,7 @@ void Community::RunInitializeCallbacks(void) num_modeltype_declarations_ = 0; // execute `species all` initialize() callbacks, which should always have a tick of 0 set - std::vector init_blocks = ScriptBlocksMatching(0, SLiMEidosBlockType::SLiMEidosInitializeCallback, -1, -1, -1, -1, -1, nullptr); + std::vector init_blocks = ScriptBlocksMatching(0, SLiMEidosBlockType::SLiMEidosInitializeCallback, -1, -1, -1, -1, -1, nullptr, /* p_active_only */ false); for (auto script_block : init_blocks) ExecuteEidosEvent(script_block); @@ -2657,7 +2663,7 @@ bool Community::_RunOneTickWF(void) #endif cycle_stage_ = SLiMCycleStage::kWFStage0ExecuteFirstScripts; - std::vector first_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventFirst, -1, -1, -1, -1, -1, nullptr); + std::vector first_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventFirst, -1, -1, -1, -1, -1, nullptr, /* p_active_only */ false); for (auto script_block : first_blocks) ExecuteEidosEvent(script_block); @@ -2689,7 +2695,7 @@ bool Community::_RunOneTickWF(void) #endif cycle_stage_ = SLiMCycleStage::kWFStage1ExecuteEarlyScripts; - std::vector early_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventEarly, -1, -1, -1, -1, -1, nullptr); + std::vector early_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventEarly, -1, -1, -1, -1, -1, nullptr, /* p_active_only */ false); for (auto script_block : early_blocks) ExecuteEidosEvent(script_block); @@ -2854,7 +2860,7 @@ bool Community::_RunOneTickWF(void) #endif cycle_stage_ = SLiMCycleStage::kWFStage5ExecuteLateScripts; - std::vector late_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventLate, -1, -1, -1, -1, -1, nullptr); + std::vector late_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventLate, -1, -1, -1, -1, -1, nullptr, /* p_active_only */ false); for (auto script_block : late_blocks) ExecuteEidosEvent(script_block); @@ -3020,7 +3026,7 @@ bool Community::_RunOneTickNonWF(void) #endif cycle_stage_ = SLiMCycleStage::kNonWFStage0ExecuteFirstScripts; - std::vector first_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventFirst, -1, -1, -1, -1, -1, nullptr); + std::vector first_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventFirst, -1, -1, -1, -1, -1, nullptr, /* p_active_only */ false); for (auto script_block : first_blocks) ExecuteEidosEvent(script_block); @@ -3156,7 +3162,7 @@ bool Community::_RunOneTickNonWF(void) #endif cycle_stage_ = SLiMCycleStage::kNonWFStage2ExecuteEarlyScripts; - std::vector early_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventEarly, -1, -1, -1, -1, -1, nullptr); + std::vector early_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventEarly, -1, -1, -1, -1, -1, nullptr, /* p_active_only */ false); for (auto script_block : early_blocks) ExecuteEidosEvent(script_block); @@ -3317,7 +3323,7 @@ bool Community::_RunOneTickNonWF(void) #endif cycle_stage_ = SLiMCycleStage::kNonWFStage6ExecuteLateScripts; - std::vector late_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventLate, -1, -1, -1, -1, -1, nullptr); + std::vector late_blocks = ScriptBlocksMatching(tick_, SLiMEidosBlockType::SLiMEidosEventLate, -1, -1, -1, -1, -1, nullptr, /* p_active_only */ false); for (auto script_block : late_blocks) ExecuteEidosEvent(script_block); diff --git a/core/community.h b/core/community.h index f5179fcf..8ed4f1dd 100644 --- a/core/community.h +++ b/core/community.h @@ -214,7 +214,7 @@ class Community : public EidosDictionaryUnretained // Managing script blocks; these two methods should be used as a matched pair, bracketing each cycle stage that calls out to script void ValidateScriptBlockCaches(void); - std::vector ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id, Species *p_species); + std::vector ScriptBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id, Species *p_species, bool p_active_only); std::vector &AllScriptBlocks(); std::vector AllScriptBlocksForSpecies(Species *p_species); void OptimizeScriptBlock(SLiMEidosBlock *p_script_block); diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index 3c82a32d..66c20047 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -752,7 +752,7 @@ EidosValue_SP Community::ExecuteMethod_deregisterScriptBlock(EidosGlobalStringID if (block->species_spec_ && ((block->type_ == SLiMEidosBlockType::SLiMEidosFitnessEffectCallback) || (block->type_ == SLiMEidosBlockType::SLiMEidosMutationEffectCallback))) { if (block->species_spec_->InsideTraitOrFitnessCalculation()) - EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_deregisterScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be deregistered within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_deregisterScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be deregistered within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); if (block->species_spec_->Active() && ((cycle_stage_ == SLiMCycleStage::kWFStage6CalculateFitness) || (cycle_stage_ == SLiMCycleStage::kNonWFStage3CalculateFitness))) EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_deregisterScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be deregistered during the fitness recalculation tick cycle stage." << EidosTerminate(); } @@ -1207,7 +1207,7 @@ EidosValue_SP Community::ExecuteMethod_rescheduleScriptBlock(EidosGlobalStringID if (block->species_spec_ && ((block->type_ == SLiMEidosBlockType::SLiMEidosFitnessEffectCallback) || (block->type_ == SLiMEidosBlockType::SLiMEidosMutationEffectCallback))) { if (block->species_spec_->InsideTraitOrFitnessCalculation()) - EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_rescheduleScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be rescheduled within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_rescheduleScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be rescheduled within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); if (block->species_spec_->Active() && ((cycle_stage_ == SLiMCycleStage::kWFStage6CalculateFitness) || (cycle_stage_ == SLiMCycleStage::kNonWFStage3CalculateFitness))) EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_rescheduleScriptBlock): fitnessEffect() and mutationEffect() callback script blocks may not be rescheduled during the fitness recalculation tick cycle stage." << EidosTerminate(); } diff --git a/core/individual.cpp b/core/individual.cpp index 06708fa9..0be4f60c 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -4384,7 +4384,7 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_haplosomesForChromosomes, kEidosValueMaskObject, gSLiM_Haplosome_Class))->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddInt_OSN("index", gStaticEidosValueNULL)->AddLogical_OS("includeNulls", gStaticEidosValue_LogicalT)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_phenotypeForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_demandPhenotype, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_demandPhenotypeForIndividuals, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setPhenotypeForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddNumeric("phenotype")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); @@ -4408,14 +4408,14 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ { switch (p_method_id) { - case gID_demandPhenotype: return ExecuteMethod_demandPhenotype(p_method_id, p_target, p_arguments, p_interpreter); - case gID_setOffsetForTrait: return ExecuteMethod_setOffsetForTrait(p_method_id, p_target, p_arguments, p_interpreter); - case gID_setPhenotypeForTrait: return ExecuteMethod_setPhenotypeForTrait(p_method_id, p_target, p_arguments, p_interpreter); - case gID_outputIndividuals: return ExecuteMethod_outputIndividuals(p_method_id, p_target, p_arguments, p_interpreter); - case gID_outputIndividualsToVCF: return ExecuteMethod_outputIndividualsToVCF(p_method_id, p_target, p_arguments, p_interpreter); - case gID_readIndividualsFromVCF: return ExecuteMethod_readIndividualsFromVCF(p_method_id, p_target, p_arguments, p_interpreter); - case gID_setSpatialPosition: return ExecuteMethod_setSpatialPosition(p_method_id, p_target, p_arguments, p_interpreter); - case gID_zygosityOfMutations: return ExecuteMethod_zygosityOfMutations(p_method_id, p_target, p_arguments, p_interpreter); + case gID_demandPhenotypeForIndividuals: return ExecuteMethod_demandPhenotypeForIndividuals(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setOffsetForTrait: return ExecuteMethod_setOffsetForTrait(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setPhenotypeForTrait: return ExecuteMethod_setPhenotypeForTrait(p_method_id, p_target, p_arguments, p_interpreter); + case gID_outputIndividuals: return ExecuteMethod_outputIndividuals(p_method_id, p_target, p_arguments, p_interpreter); + case gID_outputIndividualsToVCF: return ExecuteMethod_outputIndividualsToVCF(p_method_id, p_target, p_arguments, p_interpreter); + case gID_readIndividualsFromVCF: return ExecuteMethod_readIndividualsFromVCF(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setSpatialPosition: return ExecuteMethod_setSpatialPosition(p_method_id, p_target, p_arguments, p_interpreter); + case gID_zygosityOfMutations: return ExecuteMethod_zygosityOfMutations(p_method_id, p_target, p_arguments, p_interpreter); default: { // In a sense, we here "subclass" EidosDictionaryUnretained_Class to override setValuesVectorized(); we set a flag remembering that @@ -6360,9 +6360,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_zygosityOfMutations(EidosGlobalStr #pragma mark Phenotype demand #pragma mark - -// ********************* + (void)demandPhenotype([Niso trait = NULL], [l$ forceRecalc = F]) +// ********************* + (void)demandPhenotypeForIndividuals([Niso trait = NULL], [l$ forceRecalc = F]) // -EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotypeForIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { #pragma unused (p_method_id, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); @@ -6379,37 +6379,41 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotype(EidosGlobalStringI Species *species = Community::SpeciesForIndividuals(p_target); if (!species) - EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_demandPhenotype): demandPhenotype() requires that all individuals belong to the same species." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_demandPhenotypeForIndividuals): demandPhenotypeForIndividuals() requires that all individuals belong to the same species." << EidosTerminate(); Community &community = species->community_; // TIMING RESTRICTION - // demandPhenotype() is strictly limited to first()/early()/late() events; it cannot be called + // demandPhenotypeForIndividuals() is strictly limited to first()/early()/late() events; it cannot be called // from other contexts even for a different species than executing_species_. This is because // it can have the side effect of running mutationEffect() callbacks, and those cannot nest inside // the execution of a different species. - community.EnforceTimingRestriction_EventBlockOnly("Individual_Class::ExecuteMethod_demandPhenotype", "demandPhenotype()", ""); + community.EnforceTimingRestriction_EventBlockOnly("Individual_Class::ExecuteMethod_demandPhenotypeForIndividuals", "demandPhenotypeForIndividuals()", ""); if (species->InsideTraitOrFitnessCalculation()) - EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_demandPhenotype): demandPhenotype() cannot be called when trait/fitness calculation is already underway." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_demandPhenotypeForIndividuals): demandPhenotypeForIndividuals() cannot be called when trait/fitness calculation is already underway." << EidosTerminate(); // mark that we are doing trait calculations, to block changes to callbacks in use species->SetInsideTraitOrFitnessCalculation(true); // get the trait indices, with bounds-checking std::vector trait_indices; - species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "demandPhenotype"); + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "demandPhenotypeForIndividuals"); slim_trait_index_t trait_count = (slim_trait_index_t)trait_indices.size(); if (trait_count == 0) - return gStaticEidosValue_Float_ZeroVec; + return gStaticEidosValueVOID; // forceRecalc eidos_logical_t forceRecalc = forceRecalc_value->LogicalAtIndex_NOCAST(0, nullptr); + // validate non-neutral caches and independent-dominance precalculated values + // FIXME MULTITRAIT: VALIDATE NON-NEUTRAL CACHES HERE + + // call DemandPhenotype_INDIVIDUALS() to express the demand, across the vector of individuals if (forceRecalc) - DemandPhenotype(species, individuals_buffer, individuals_count, trait_indices); + DemandPhenotype_INDIVIDUALS(species, individuals_buffer, individuals_count, trait_indices); else - DemandPhenotype(species, individuals_buffer, individuals_count, trait_indices); + DemandPhenotype_INDIVIDUALS(species, individuals_buffer, individuals_count, trait_indices); // done with trait calculations, unblock species->SetInsideTraitOrFitnessCalculation(false); @@ -6422,8 +6426,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotype(EidosGlobalStringI return gStaticEidosValueVOID; } +// This version of DemandPhenotype is called for a vector of individuals. This is called by the Individual method demandPhenotypeForIndividuals(). template -void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const +void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) { // Given a vector of individuals that are all guaranteed to belong to the provided species, and a vector of // trait indices guaranteed to be of length 1 or longer, this method loops over the chromosomes of the @@ -6437,7 +6442,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual // First we cache a vector of mutationEffect() callbacks for each subpop; we do this here, rather than at the // start of each tick, so that newly registered callbacks function, and the current active state of each // callback is respected. - std::vector mutationEffect_callbacks = species->CallbackBlocksMatching(species->community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1); + std::vector mutationEffect_callbacks = species->CallbackBlocksMatching(species->community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); Population &population = species->population_; bool has_active_callbacks = false; slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); @@ -6458,20 +6463,20 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual Subpopulation *subpop = subpop_pair.second; if ((int)subpop->per_trait_subpop_caches_.size() != species->TraitCount()) - EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ is not correctly sized." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) per_trait_subpop_caches_ is not correctly sized." << EidosTerminate(); for (slim_trait_index_t trait_index = 0; trait_index < species->TraitCount(); trait_index++) { Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; if (subpop_trait_caches.mutationEffect_callbacks_per_trait.size() != 0) - EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ mutationEffect_callbacks_per_trait_ is not empty." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) per_trait_subpop_caches_ mutationEffect_callbacks_per_trait_ is not empty." << EidosTerminate(); if (subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED != nullptr) - EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ IncorporateEffects_Haploid_TEMPLATED is not nullptr." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) per_trait_subpop_caches_ IncorporateEffects_Haploid_TEMPLATED is not nullptr." << EidosTerminate(); if (subpop_trait_caches.IncorporateEffects_Hemizygous_TEMPLATED != nullptr) - EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ IncorporateEffects_Hemizygous_TEMPLATED is not nullptr." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) per_trait_subpop_caches_ IncorporateEffects_Hemizygous_TEMPLATED is not nullptr." << EidosTerminate(); if (subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED != nullptr) - EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype): (internal error) per_trait_subpop_caches_ IncorporateEffects_Diploid_TEMPLATED is not nullptr." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) per_trait_subpop_caches_ IncorporateEffects_Diploid_TEMPLATED is not nullptr." << EidosTerminate(); } } #endif @@ -6489,7 +6494,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual // if (has_active_callbacks) { - // If we have any active callbacks, we have to account for all callbacks (active or not), since + // If we have any active callbacks, we have to account for all callbacks (active or not), since // FIXME MULTITRAIT this is not true any more! // one callback might activate/deactivate another; inactive callbacks might not stay inactive. // This callback applies to this subpopulation. We now need to determine which traits, if any, it applies to. // For each trait we keep a separate vector of callbacks that apply to that trait. @@ -6857,7 +6862,7 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual } if (species->DoingAnyMutationRunExperiments()) - chromosome->StopMutationRunExperimentClock("DemandPhenotype()"); + chromosome->StopMutationRunExperimentClock("DemandPhenotype_INDIVIDUALS()"); } // clear out each subpopulation's per-trait caches that we set up above; these are only for our private use @@ -6878,13 +6883,304 @@ void Individual_Class::DemandPhenotype(Species *species, Individual **individual } } -template void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; -template void Individual_Class::DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; +template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices); +template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices); + + +// This version of DemandPhenotype is called for a whole subpopulation. This allows for greater efficiency than the individual-level version of this method. +// This is called by the Subpopulation method demandPhenotype(), and by SLiM's internal fitness calculation code for traits with a direct effect on fitness. +template +void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector p_subpop_mutationEffect_callbacks) +{ +#warning DemandPhenotype_SUBPOP() -- implement me! + // FIXME MULTITRAIT: think about shuffling the order in which individuals are handled, in this code path + + // Given a subpopulation `subpop` that is guaranteed to belong to the provided species, and a vector of + // trait indices guaranteed to be of length 1 or longer, this method loops over the chromosomes of the + // species (the top-level loop to make mutation run experiment timing simple), then over the traits provided + // (to avoid having to test for additive vs. multiplicative over and over for each mutation), then over + // the individuals (the level at which parallelization of the code occurs). For each individual, it + // dispatches to a sub-method that computes the trait value produced by all of the mutations for the given + // chromosome/trait/individual. This method then aggregates those values together to produce the final + // trait values, which are saved into the phenotype storage of each individual. + + // This method is passed p_subpop_mutationEffect_callbacks, a subpop-specific set of mutationEffect() + // callbacks, and does not use the subpopulation per-trait caches used by DemandPhenotype_INDIVIDUALS(). + Individual **individuals_buffer = subpop->parent_individuals_.data(); + int individuals_count = subpop->parent_subpop_size_; + slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); + + // FIXME MULTITRAIT: validate non-neutral caches and independent-dominance effects here +#warning re-enable non-neutral caches and recache non-neutral caches first + + // For a given individual, for a given trait, we have to make a decision as to whether we will recalculate or not. That decision gets made + // once and then holds across all chromosomes for the individual. But we're looping over chromosomes at the topmost level, so we have a + // little problem: how will we remember whether we decided to recalculate a given individual/trait when we get to doing the work for + // successive chromosomes? We have to keep a vector of flags, actually; there's no choice but to keep that state somewhere. So, we use + // std::vector with # individuals x # traits flags in it. Here we loop through individuals and traits, making decisions about whether + // we're recalculating each trait in each individual. For f_force_recalc == true that decision is always YES, so this method is templated + // to avoid all overhead completely in that case. For each phenotype that we do intend to recalculate, we set its initial value from the + // baseline offset and individual offset for the trait. + // FIXME MULTITRAIT: if we have just one chromosome and one trait, we don't need this std::vector, because the decision is only needed + // once per individual; that should probably be special-cased, since this bool vector thing is gross and heavyweight. + std::vector recalc_decisions; + + if (!f_force_recalc) + recalc_decisions.resize(individuals_count * trait_indices.size()); // zero-fills to false + + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; + TraitType traitType = trait->Type(); + slim_effect_t trait_baseline_offset = trait->BaselineOffset(); + + if (traitType == TraitType::kAdditive) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; + + if (f_force_recalc) + { + trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + } + else if (!f_force_recalc && std::isnan(trait_info.phenotype_)) + { + recalc_decisions[individual_index * trait_indices_count + trait_indices_index] = true; + trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + } + // else (!f_force_recalc && !std::isnan(trait_info.phenotype_)), so we are not recalculating + } + } + else // (traitType == TraitType::kMultiplicative) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; + + if (f_force_recalc) + { + trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + } + else if (!f_force_recalc && std::isnan(trait_info.phenotype_)) + { + recalc_decisions[individual_index * trait_indices_count + trait_indices_index] = true; + trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + } + // else (!f_force_recalc && !std::isnan(trait_info.phenotype_)), so we are not recalculating + } + } + } + + // calculate the specified phenotypes for the individual; this loops through all chromosomes, handling ploidy and callbacks as needed + // it is very nice to have the top-level loop be over the chromosomes, so that each one can do a single timing for mutrun experiments + int haplosome_index = 0; + + for (Chromosome *chromosome : species->Chromosomes()) + { + if (species->DoingAnyMutationRunExperiments()) + chromosome->StartMutationRunExperimentClock(); + + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; + TraitType traitType = trait->Type(); + + // Cache the mutationEffect() callbacks that are relevant to this trait + std::vector subpop_per_trait_mutationEffect_callbacks; + + for (SLiMEidosBlock *callback : p_subpop_mutationEffect_callbacks) + { + // check if this callback applies to this trait + slim_trait_index_t callback_trait_index = callback->trait_index_; + + if ((callback_trait_index == -1) || (callback_trait_index == trait_index)) + subpop_per_trait_mutationEffect_callbacks.emplace_back(callback); + } + + // FIXME MULTITRAIT: could determine at this point whether our callbacks force a fixed effect, + // and whether that effect is neutral; if so, we could skip all remaining genetic work. Similarly, + // this would be the point at which to decide to use independent-dominance effects and skip the + // genetic work. There is some redundancy to doing those determination inside the loop over + // chromosomes, though, unless we add per-chromosome flags about neutrality and independent + // dominance; maybe we can/should make such determinations outside the chromosome loop? + + int mutationEffect_callback_count = (int)subpop_per_trait_mutationEffect_callbacks.size(); + bool mutationEffect_callbacks_exist = (mutationEffect_callback_count > 0); + bool single_mutationEffect_callback = (mutationEffect_callback_count == 1); + + // Cache method pointers for haploid and diploid chromosomes for this trait. These are templated + // for efficiency, so we have to choose the correct template. That depends on whether the trait is + // additive or multiplicative; it could potentially also depend on per-trait callbacks (FIXME MULTITRAIT). + // Note the template for _IncorporateEffects_Haploid() (which handles both haploid and hemizygous cases): + // + // template + // + // and the template for _IncorporateEffects_Diploid() (which handles the non-hemizygous diploid case): + // + // template < const bool f_additiveTrait, const bool f_callbacks, const bool f_singlecallback> + // + void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Hemizygous_TEMPLATED)(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Diploid_TEMPLATED)(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + + if (!mutationEffect_callbacks_exist) + { + if (traitType == TraitType::kAdditive) + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + else + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + } + else if (single_mutationEffect_callback) + { + if (traitType == TraitType::kAdditive) + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + else + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + } + else + { + if (traitType == TraitType::kAdditive) + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + else + { + IncorporateEffects_Haploid_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Hemizygous_TEMPLATED = &Individual::_IncorporateEffects_Haploid; + IncorporateEffects_Diploid_TEMPLATED = &Individual::_IncorporateEffects_Diploid; + } + } + + // Then process the chromosome for the focal trait + switch (chromosome->Type()) + { + // diploid, possibly with one or both being null haplosomes + case ChromosomeType::kA_DiploidAutosome: + case ChromosomeType::kX_XSexChromosome: + case ChromosomeType::kZ_ZSexChromosome: + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + Haplosome *haplosome1 = ind->haplosomes_[haplosome_index]; + Haplosome *haplosome2 = ind->haplosomes_[haplosome_index+1]; + + if (haplosome1->IsNull()) + { + if (!haplosome2->IsNull()) + { + // hemizygous (haplosome2) + (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome2, trait_index, subpop_per_trait_mutationEffect_callbacks); + } + else + { + // both haplosomes are null (only happens with chromosome type "A"; no work to be done + } + } + else if (haplosome2->IsNull()) + { + // hemizygous (haplosome1) + (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome1, trait_index, subpop_per_trait_mutationEffect_callbacks); + } + else + { + // diploid, both haplosomes non-null + (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait_index, subpop_per_trait_mutationEffect_callbacks); + } + } + + haplosome_index += 2; + break; + } + + // haploid, possibly null + case ChromosomeType::kH_HaploidAutosome: + case ChromosomeType::kY_YSexChromosome: + case ChromosomeType::kW_WSexChromosome: + case ChromosomeType::kHF_HaploidFemaleInherited: + case ChromosomeType::kFL_HaploidFemaleLine: + case ChromosomeType::kHM_HaploidMaleInherited: + case ChromosomeType::kML_HaploidMaleLine: + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + Haplosome *haplosome = ind->haplosomes_[haplosome_index]; + + if (haplosome->IsNull()) + continue; + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_per_trait_mutationEffect_callbacks); + } + + haplosome_index += 1; + break; + } + + // haploid special cases that have an accompanying null haplosome for backward compatibility + case ChromosomeType::kHNull_HaploidAutosomeWithNull: + case ChromosomeType::kNullY_YSexChromosomeWithNull: + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + Haplosome *haplosome = ind->haplosomes_[haplosome_index + ((chromosome->Type() == ChromosomeType::kNullY_YSexChromosomeWithNull) ? 1 : 0)]; + + if (haplosome->IsNull()) + continue; + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_per_trait_mutationEffect_callbacks); + } + + haplosome_index += 2; + break; + } + } + } + + if (species->DoingAnyMutationRunExperiments()) + chromosome->StopMutationRunExperimentClock("DemandPhenotype_SUBPOP()"); + } +} + +template void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector p_mutationEffect_callbacks); +template void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector p_mutationEffect_callbacks); // Low-level method to calculate a phenotype for one individual, for one haploid (or hemizygous) chromosome, // for one trait. This will put the result of the calculation into the individual's phenotype information. -// This is called by Individual_Class::DemandPhenotype(), which loops over chromosomes, traits, and individuals. +// This is called by Individual_Class::DemandPhenotype_X(), which loops over chromosomes, traits, and individuals. template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) { @@ -6982,7 +7278,7 @@ template void Individual::_IncorporateEffects_Haploid(Sp // Low-level method to calculate a phenotype for one individual, for one diploid chromosome, for one trait. // This will put the result of the calculation into the individual's phenotype information. This is called -// by Individual_Class::DemandPhenotype(), which loops over chromosomes, traits, and individuals. +// by Individual_Class::DemandPhenotype_X(), which loops over chromosomes, traits, and individuals. template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) { diff --git a/core/individual.h b/core/individual.h index 1806560e..5e973722 100644 --- a/core/individual.h +++ b/core/individual.h @@ -410,7 +410,7 @@ class Individual : public EidosDictionaryUnretained // phenotype demand for a single trait in a single individual, across a single chromosome; the result is // accumulated into the trait value of the focal individual, which must be set up with an initial value - // see also the method DemandPhenotype() in class Individual_Class, which calls these methods + // see also the DemandPhenotype_X() methods in class Individual_Class, which call these methods template void _IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); @@ -443,13 +443,16 @@ class Individual_Class : public EidosDictionaryUnretained_Class EidosValue_SP ExecuteMethod_setSpatialPosition(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_zygosityOfMutations(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; - EidosValue_SP ExecuteMethod_demandPhenotype(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + EidosValue_SP ExecuteMethod_demandPhenotypeForIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; // phenotype demand for all traits for a vector of target individuals, across all chromosomes // if f_force_recalc is true all values are recalculated; if false, only NAN trait values are recalculated // see also the methods class _IncorporateEffects_X() methods in class Individual, called by this method template - void DemandPhenotype(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) const; + static void DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices); + + template + static void DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector p_subpop_mutationEffect_callbacks); }; diff --git a/core/interaction_type.cpp b/core/interaction_type.cpp index b4453da3..74c0961c 100755 --- a/core/interaction_type.cpp +++ b/core/interaction_type.cpp @@ -514,7 +514,7 @@ void InteractionType::EvaluateSubpopulation(Subpopulation *p_subpop) // Note that interaction() callbacks are non-species-specific, so we fetch from the Community with species nullptr. // Callbacks used depend upon the exerter subpopulation, so this is snapping the callbacks for subpop as exerters; // the subpopulation of receivers does not influence the choice of which callbacks are used. - subpop_data->evaluation_interaction_callbacks_ = community_.ScriptBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosInteractionCallback, -1, interaction_type_id_, subpop_id, -1, -1, nullptr); + subpop_data->evaluation_interaction_callbacks_ = community_.ScriptBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosInteractionCallback, -1, interaction_type_id_, subpop_id, -1, -1, nullptr, /* p_active_only */ false); // Note that we do not create the k-d tree here. Non-spatial models will never have a k-d tree; spatial models may or // may not need one, depending upon what methods are called by the client, which may vary cycle by cycle. diff --git a/core/population.cpp b/core/population.cpp index 54d6f589..126fde4e 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5278,23 +5278,11 @@ void Population::RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recal // calculate the fitnesses of the parents and make lookup tables; the main thing we do here is manage the mutationEffect() callbacks // as per the SLiM design spec, we get the list of callbacks once, and use that list throughout this stage, but we construct // subsets of it for each subpopulation, so that UpdateFitness() can just use the callback list as given to it - std::vector mutationEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1); - std::vector fitnessEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, -1, -1, -1); - bool no_active_callbacks = true; + std::vector mutationEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); + std::vector fitnessEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); + bool no_active_callbacks = (mutationEffect_callbacks.size() == 0) && (fitnessEffect_callbacks.size() == 0); - for (SLiMEidosBlock *callback : mutationEffect_callbacks) - if (callback->block_active_) - { - no_active_callbacks = false; - break; - } - if (no_active_callbacks) - for (SLiMEidosBlock *callback : fitnessEffect_callbacks) - if (callback->block_active_) - { - no_active_callbacks = false; - break; - } + // FIXME MULTITRAIT: the "regime" logic here needs to adapt to the fact that we only fetch active callbacks now; it is more conservative than it now needs to be! // Figure out how we are going to handle MutationRun nonneutral mutation caches; see mutation_run.h. We need to assess // the state of callbacks and decide which of the three "regimes" we are in, and then depending upon that and what diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index 6fdc2e4e..4f29a43b 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -1760,7 +1760,7 @@ void SLiMEidosBlock::SetProperty(EidosGlobalStringID p_property_id, const EidosV if (species_spec_ && ((type_ == SLiMEidosBlockType::SLiMEidosFitnessEffectCallback) || (type_ == SLiMEidosBlockType::SLiMEidosMutationEffectCallback))) { if (species_spec_->InsideTraitOrFitnessCalculation()) - EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SetProperty): the active property of fitnessEffect() and mutationEffect() callback script blocks may not be set within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SetProperty): the active property of fitnessEffect() and mutationEffect() callback script blocks may not be set within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); if (species_spec_->Active() && ((species_spec_->community_.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (species_spec_->community_.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SetProperty): the active property of fitnessEffect() and mutationEffect() callback script blocks may not be set during the fitness recalculation tick cycle stage." << EidosTerminate(); } diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 879d5a18..0b65c01c 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1363,7 +1363,7 @@ const std::string &gStr_containsMarkerMutation = EidosRegisteredString("contains const std::string &gStr_haplosomesForChromosomes = EidosRegisteredString("haplosomesForChromosomes", gID_haplosomesForChromosomes); const std::string &gStr_offsetForTrait = EidosRegisteredString("offsetForTrait", gID_offsetForTrait); const std::string &gStr_phenotypeForTrait = EidosRegisteredString("phenotypeForTrait", gID_phenotypeForTrait); -const std::string &gStr_demandPhenotype = EidosRegisteredString("demandPhenotype", gID_demandPhenotype); +const std::string &gStr_demandPhenotypeForIndividuals = EidosRegisteredString("demandPhenotypeForIndividuals", gID_demandPhenotypeForIndividuals); const std::string &gStr_setOffsetForTrait = EidosRegisteredString("setOffsetForTrait", gID_setOffsetForTrait); const std::string &gStr_setPhenotypeForTrait = EidosRegisteredString("setPhenotypeForTrait", gID_setPhenotypeForTrait); const std::string &gStr_relatedness = EidosRegisteredString("relatedness", gID_relatedness); @@ -1416,6 +1416,7 @@ const std::string &gStr_scriptBlocksWithIDs = EidosRegisteredString("scriptBlock const std::string &gStr_speciesWithIDs = EidosRegisteredString("speciesWithIDs", gID_speciesWithIDs); const std::string &gStr_subpopulationsWithIDs = EidosRegisteredString("subpopulationsWithIDs", gID_subpopulationsWithIDs); const std::string &gStr_subpopulationsWithNames = EidosRegisteredString("subpopulationsWithNames", gID_subpopulationsWithNames); +const std::string &gStr_demandPhenotype = EidosRegisteredString("demandPhenotype", gID_demandPhenotype); const std::string &gStr_individualsWithPedigreeIDs = EidosRegisteredString("individualsWithPedigreeIDs", gID_individualsWithPedigreeIDs); const std::string &gStr_killIndividuals = EidosRegisteredString("killIndividuals", gID_killIndividuals); const std::string &gStr_mutationCounts = EidosRegisteredString("mutationCounts", gID_mutationCounts); diff --git a/core/slim_globals.h b/core/slim_globals.h index 53d7012b..a46f993d 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -953,7 +953,7 @@ extern const std::string &gStr_containsMarkerMutation; extern const std::string &gStr_haplosomesForChromosomes; extern const std::string &gStr_offsetForTrait; extern const std::string &gStr_phenotypeForTrait; -extern const std::string &gStr_demandPhenotype; +extern const std::string &gStr_demandPhenotypeForIndividuals; extern const std::string &gStr_setOffsetForTrait; extern const std::string &gStr_setPhenotypeForTrait; extern const std::string &gStr_relatedness; @@ -1006,6 +1006,7 @@ extern const std::string &gStr_scriptBlocksWithIDs; extern const std::string &gStr_speciesWithIDs; extern const std::string &gStr_subpopulationsWithIDs; extern const std::string &gStr_subpopulationsWithNames; +extern const std::string &gStr_demandPhenotype; extern const std::string &gStr_individualsWithPedigreeIDs; extern const std::string &gStr_killIndividuals; extern const std::string &gStr_mutationCounts; @@ -1441,7 +1442,7 @@ enum _SLiMGlobalStringID : int { gID_haplosomesForChromosomes, gID_offsetForTrait, gID_phenotypeForTrait, - gID_demandPhenotype, + gID_demandPhenotypeForIndividuals, gID_setOffsetForTrait, gID_setPhenotypeForTrait, gID_relatedness, @@ -1494,6 +1495,7 @@ enum _SLiMGlobalStringID : int { gID_speciesWithIDs, gID_subpopulationsWithIDs, gID_subpopulationsWithNames, + gID_demandPhenotype, gID_individualsWithPedigreeIDs, gID_killIndividuals, gID_mutationCounts, diff --git a/core/species.cpp b/core/species.cpp index cb403038..174afba8 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -2673,11 +2673,11 @@ void Species::DeleteAllMutationRuns(void) #pragma mark Running cycles #pragma mark - -std::vector Species::CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id) +std::vector Species::CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id, bool p_active_only) { // Callbacks are species-specific; this method calls up to the community, which manages script blocks, // but does a species-specific search. - return community_.ScriptBlocksMatching(p_tick, p_event_type, p_mutation_type_id, p_interaction_type_id, p_subpopulation_id, p_trait_index, p_chromosome_id, this); + return community_.ScriptBlocksMatching(p_tick, p_event_type, p_mutation_type_id, p_interaction_type_id, p_subpopulation_id, p_trait_index, p_chromosome_id, this, p_active_only); } void Species::RunInitializeCallbacks(void) @@ -2704,7 +2704,7 @@ void Species::RunInitializeCallbacks(void) has_implicit_chromosome_ = false; // execute initialize() callbacks, which should always have a tick of 0 set - std::vector init_blocks = CallbackBlocksMatching(0, SLiMEidosBlockType::SLiMEidosInitializeCallback, -1, -1, -1, -1, -1); + std::vector init_blocks = CallbackBlocksMatching(0, SLiMEidosBlockType::SLiMEidosInitializeCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); for (auto script_block : init_blocks) community_.ExecuteEidosEvent(script_block); @@ -3111,10 +3111,10 @@ void Species::EmptyGraveyard(void) void Species::WF_GenerateOffspring(void) { slim_tick_t tick = community_.Tick(); - std::vector mate_choice_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMateChoiceCallback, -1, -1, -1, -1, -1); - std::vector modify_child_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosModifyChildCallback, -1, -1, -1, -1, -1); - std::vector recombination_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, -1, -1, -1); - std::vector mutation_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMutationCallback, -1, -1, -1, -1, -1); + std::vector mate_choice_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMateChoiceCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); + std::vector modify_child_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosModifyChildCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); + std::vector recombination_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); + std::vector mutation_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMutationCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); bool mate_choice_callbacks_present = mate_choice_callbacks.size(); bool modify_child_callbacks_present = modify_child_callbacks.size(); bool recombination_callbacks_present = recombination_callbacks.size(); @@ -3253,10 +3253,10 @@ void Species::WF_SwapGenerations(void) void Species::nonWF_GenerateOffspring(void) { slim_tick_t tick = community_.Tick(); - std::vector reproduction_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosReproductionCallback, -1, -1, -1, -1, -1); - std::vector modify_child_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosModifyChildCallback, -1, -1, -1, -1, -1); - std::vector recombination_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, -1, -1, -1); - std::vector mutation_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMutationCallback, -1, -1, -1, -1, -1); + std::vector reproduction_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosReproductionCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); + std::vector modify_child_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosModifyChildCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); + std::vector recombination_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosRecombinationCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); + std::vector mutation_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosMutationCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); // choose templated variants for GenerateIndividualsX() methods of Subpopulation, called during reproduction() callbacks // this is an optimization technique that lets us optimize away unused cruft at compile time @@ -3694,7 +3694,7 @@ void Species::nonWF_MergeOffspring(void) void Species::nonWF_ViabilitySurvival(void) { slim_tick_t tick = community_.Tick(); - std::vector survival_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosSurvivalCallback, -1, -1, -1, -1, -1); + std::vector survival_callbacks = CallbackBlocksMatching(tick, SLiMEidosBlockType::SLiMEidosSurvivalCallback, -1, -1, -1, -1, -1, /* p_active_only */ false); bool survival_callbacks_present = survival_callbacks.size(); bool no_active_callbacks = true; diff --git a/core/species.h b/core/species.h index de2ee0f2..884bb52e 100644 --- a/core/species.h +++ b/core/species.h @@ -509,7 +509,7 @@ class Species : public EidosDictionaryUnretained void DeleteAllMutationRuns(void); // for cleanup // Running cycles - std::vector CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id); + std::vector CallbackBlocksMatching(slim_tick_t p_tick, SLiMEidosBlockType p_event_type, slim_objectid_t p_mutation_type_id, slim_objectid_t p_interaction_type_id, slim_objectid_t p_subpopulation_id, slim_trait_index_t p_trait_index, int64_t p_chromosome_id, bool p_active_only); void RunInitializeCallbacks(void); void CreateAndPromulgateMutationBlock(void); void EndCurrentChromosome(bool starting_new_chromosome); @@ -731,6 +731,7 @@ class Species : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_chromosomesWithSymbols(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_traitsWithIndices(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_traitsWithNames(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_demandPhenotype(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_individualsWithPedigreeIDs(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_killIndividuals(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_mutationFreqsCounts(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index b44bccd3..3fea7ba6 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -2411,6 +2411,7 @@ EidosValue_SP Species::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, co case gID_chromosomesWithSymbols: return ExecuteMethod_chromosomesWithSymbols(p_method_id, p_arguments, p_interpreter); case gID_traitsWithIndices: return ExecuteMethod_traitsWithIndices(p_method_id, p_arguments, p_interpreter); case gID_traitsWithNames: return ExecuteMethod_traitsWithNames(p_method_id, p_arguments, p_interpreter); + case gID_demandPhenotype: return ExecuteMethod_demandPhenotype(p_method_id, p_arguments, p_interpreter); case gID_individualsWithPedigreeIDs: return ExecuteMethod_individualsWithPedigreeIDs(p_method_id, p_arguments, p_interpreter); case gID_killIndividuals: return ExecuteMethod_killIndividuals(p_method_id, p_arguments, p_interpreter); case gID_mutationFrequencies: @@ -3084,6 +3085,101 @@ EidosValue_SP Species::ExecuteMethod_traitsWithNames(EidosGlobalStringID p_metho return EidosValue_SP(result); } +// ********************* – (void)demandPhenotype(Nio subpops, [Niso trait = NULL], [l$ forceRecalc = F]) +EidosValue_SP Species::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_interpreter) + EidosValue *subpops_value = p_arguments[0].get(); + EidosValue *trait_value = p_arguments[1].get(); + EidosValue *forceRecalc_value = p_arguments[2].get(); + + // TIMING RESTRICTION + // demandPhenotypeForIndividuals() is strictly limited to first()/early()/late() events; it cannot be called + // from other contexts even for a different species than executing_species_. This is because + // it can have the side effect of running mutationEffect() callbacks, and those cannot nest inside + // the execution of a different species. + community_.EnforceTimingRestriction_EventBlockOnly("Species::ExecuteMethod_demandPhenotype", "demandPhenotype()", ""); + if (InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_demandPhenotype): demandPhenotype() cannot be called when trait/fitness calculation is already underway." << EidosTerminate(); + + // mark that we are doing trait calculations, to block changes to callbacks in use + SetInsideTraitOrFitnessCalculation(true); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + GetTraitIndicesFromEidosValue(trait_indices, trait_value, "demandPhenotype"); + slim_trait_index_t trait_count = (slim_trait_index_t)trait_indices.size(); + + if (trait_count == 0) + return gStaticEidosValueVOID; + + // forceRecalc + eidos_logical_t forceRecalc = forceRecalc_value->LogicalAtIndex_NOCAST(0, nullptr); + + // subpops + std::vector subpops_to_demand; + + if (subpops_value->Type() == EidosValueType::kValueNULL) + { + // demand the specified phenotypes across all subpopulations + subpops_to_demand.resize(population_.subpops_.size()); + + for (const std::pair &subpop_pair : population_.subpops_) + subpops_to_demand.push_back(subpop_pair.second); + } + else + { + // requested subpops, so get them + int requested_subpop_count = subpops_value->Count(); + + if (requested_subpop_count) + { + subpops_to_demand.resize(requested_subpop_count); + + for (int requested_subpop_index = 0; requested_subpop_index < requested_subpop_count; ++requested_subpop_index) + subpops_to_demand.emplace_back(SLiM_ExtractSubpopulationFromEidosValue_io(subpops_value, requested_subpop_index, &community_, this, "demandPhenotype()")); // SPECIES CONSISTENCY CHECK + + // unique subpops_to_demand to avoid duplicated work + std::sort(subpops_to_demand.begin(), subpops_to_demand.end()); + subpops_to_demand.resize(static_cast(std::distance(subpops_to_demand.begin(), std::unique(subpops_to_demand.begin(), subpops_to_demand.end())))); + } + } + + // validate non-neutral caches and independent-dominance precalculated values + // FIXME MULTITRAIT: VALIDATE NON-NEUTRAL CACHES HERE + + // call DemandPhenotype_SUBPOP() to express the demand, one subpop at a time + std::vector mutationEffect_callbacks = Species::CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); + std::vector subpop_mutationEffect_callbacks; + + for (Subpopulation *subpop : subpops_to_demand) + { + // narrow down to the mutationEffect() callbacks for subpop + subpop_mutationEffect_callbacks.resize(0); + + for (SLiMEidosBlock *callback : mutationEffect_callbacks) + { + if ((callback->subpopulation_id_ == -1) || (callback->subpopulation_id_ == subpop->subpopulation_id_)) + subpop_mutationEffect_callbacks.push_back(callback); + } + + if (forceRecalc) + Individual_Class::DemandPhenotype_SUBPOP(this, subpop, trait_indices, subpop_mutationEffect_callbacks); + else + Individual_Class::DemandPhenotype_SUBPOP(this, subpop, trait_indices, subpop_mutationEffect_callbacks); + } + + // done with trait calculations, unblock + SetInsideTraitOrFitnessCalculation(false); + + // BCH 12/25/2025: I considered having this return the trait values that were demanded; but I think void is + // better. Collecting the trait values would be additional work here that would not always be desired, so + // it's better to make the user do it separately if they want it. Also, this method should generally be + // called once to demand a set of traits, but getting a whole set of trait values back -- as an interleaved + // vector or a matrix -- is pretty inconvenient to work with. + return gStaticEidosValueVOID; +} + // ********************* – (object)individualsWithPedigreeIDs(integer pedigreeIDs, [Nio subpops = NULL]) EidosValue_SP Species::ExecuteMethod_individualsWithPedigreeIDs(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -4017,7 +4113,7 @@ EidosValue_SP Species::ExecuteMethod_registerFitnessEffectCallback(EidosGlobalSt // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu if (InsideTraitOrFitnessCalculation()) - EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerFitnessEffectCallback): fitnessEffect() callback script blocks may not be registered within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerFitnessEffectCallback): fitnessEffect() callback script blocks may not be registered within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); if (Active() && ((community_.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community_.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerFitnessEffectCallback): fitnessEffect() callback script blocks may not be registered during the fitness recalculation tick cycle stage." << EidosTerminate(); @@ -4175,7 +4271,7 @@ EidosValue_SP Species::ExecuteMethod_registerMutationEffectCallback(EidosGlobalS // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu if (InsideTraitOrFitnessCalculation()) - EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerMutationEffectCallback): mutationEffect() callback script blocks may not be registered within the context of a call to demandPhenotype() or recalculateFitness()." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerMutationEffectCallback): mutationEffect() callback script blocks may not be registered within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); if (Active() && ((community_.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community_.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) EIDOS_TERMINATION << "ERROR (Community::ExecuteMethod_registerMutationEffectCallback): mutationEffect() callback script blocks may not be registered during the fitness recalculation tick cycle stage." << EidosTerminate(); @@ -4748,6 +4844,7 @@ const std::vector *Species_Class::Methods(void) const methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_chromosomesWithIDs, kEidosValueMaskObject, gSLiM_Chromosome_Class))->AddInt("ids")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_chromosomesWithSymbols, kEidosValueMaskObject, gSLiM_Chromosome_Class))->AddString("symbols")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_countOfMutationsOfType, kEidosValueMaskInt | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_demandPhenotype, kEidosValueMaskVOID))->AddIntObject_N("subpops", gSLiM_Subpopulation_Class)->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_individualsWithPedigreeIDs, kEidosValueMaskObject, gSLiM_Individual_Class))->AddInt("pedigreeIDs")->AddIntObject_ON("subpops", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_killIndividuals, kEidosValueMaskVOID))->AddObject("individuals", gSLiM_Individual_Class)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_mutationCounts, kEidosValueMaskInt))->AddIntObject_N("subpops", gSLiM_Subpopulation_Class)->AddObject_ON("mutations", gSLiM_Mutation_Class, gStaticEidosValueNULL)); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 8d5842cb..f488221b 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1356,7 +1356,7 @@ void Subpopulation::FixNonNeutralCaches_OMP(void) // calls UpdateFitness() on each subpopulation. This method expresses demand for the traits in question, and // then produces fitness values by factoring in fitnessEffect() callbacks and fitnessScaling values. It stores // the fitness values in the appropriate places to prepare for their later use. -void Subpopulation::UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation) +void Subpopulation::UpdateFitness(std::vector &p_subpop_mutationEffect_callbacks, std::vector &p_subpop_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation) { // Determine whether we are in a "pure neutral" case where we don't need to calculate individual fitness // because all individuals have neutral fitness. The simplest case where this is true is if there are no @@ -1425,80 +1425,78 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect // demand phenotypes for all the relevant traits if (p_direct_effect_trait_indices.size()) { -#warning make a new DemandPhenotype() function for whole subpops -#warning need to think about shuffling the order for DemandPhenotype as well! if (p_force_trait_recalculation) - gSLiM_Individual_Class->DemandPhenotype(&species_, parent_individuals_.data(), (int)parent_individuals_.size(), p_direct_effect_trait_indices); // FIXME MULTITRAIT: pass in p_mutationEffect_callbacks to a per-subpop version of this + Individual_Class::DemandPhenotype_SUBPOP(&species_, this, p_direct_effect_trait_indices, p_subpop_mutationEffect_callbacks); else - gSLiM_Individual_Class->DemandPhenotype(&species_, parent_individuals_.data(), (int)parent_individuals_.size(), p_direct_effect_trait_indices); // FIXME MULTITRAIT: pass in p_mutationEffect_callbacks to a per-subpop version of this + Individual_Class::DemandPhenotype_SUBPOP(&species_, this, p_direct_effect_trait_indices, p_subpop_mutationEffect_callbacks); } // then loop over individuals and pull together the relevant phenotype values, fitnessEffect() callbacks, // subpopulation fitnessScaling, and individual fitnessScaling to produce final individual fitness values; - // we choose our _UpdateFitness() template based upon flags and execute it to calculate fitness values + // we choose our _CalculateFitnessAfterDemand() template based upon flags and execute it to calculate fitness values bool f_has_subpop_fitnessScaling = (subpop_fitness_scaling != 1.0f); bool f_has_ind_fitnessScaling = Individual::s_any_individual_fitness_scaling_set_; - bool f_has_fitnessEffect_callbacks = (p_fitnessEffect_callbacks.size() > 0); + bool f_has_fitnessEffect_callbacks = (p_subpop_fitnessEffect_callbacks.size() > 0); bool f_has_trait_direct_effects = (p_direct_effect_trait_indices.size() > 0); bool f_single_trait = (p_direct_effect_trait_indices.size() == 1); - void (Subpopulation::*_UpdateFitness_TEMPLATED)(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) = nullptr; + void (Subpopulation::*_CalculateFitnessAfterDemand_TEMPLATED)(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) = nullptr; if (f_has_subpop_fitnessScaling) { if (f_has_ind_fitnessScaling) { if (f_has_fitnessEffect_callbacks) { if (f_has_trait_direct_effects) { - if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + if (f_single_trait) _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + } else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; } else { if (f_has_trait_direct_effects) { - if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + if (f_single_trait) _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + } else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; } } else { if (f_has_fitnessEffect_callbacks) { if (f_has_trait_direct_effects) { - if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + if (f_single_trait) _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + } else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; } else { if (f_has_trait_direct_effects) { - if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + if (f_single_trait) _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + } else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; } } } else { if (f_has_ind_fitnessScaling) { if (f_has_fitnessEffect_callbacks) { if (f_has_trait_direct_effects) { - if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + if (f_single_trait) _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + } else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; } else { if (f_has_trait_direct_effects) { - if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + if (f_single_trait) _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + } else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; } } else { if (f_has_fitnessEffect_callbacks) { if (f_has_trait_direct_effects) { - if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + if (f_single_trait) _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + } else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; } else { if (f_has_trait_direct_effects) { - if (f_single_trait) _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; - } else _UpdateFitness_TEMPLATED = &Subpopulation::_UpdateFitness; + if (f_single_trait) _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; + } else _CalculateFitnessAfterDemand_TEMPLATED = &Subpopulation::_CalculateFitnessAfterDemand; } } } - (this->*(_UpdateFitness_TEMPLATED))(p_fitnessEffect_callbacks, p_direct_effect_trait_indices); + (this->*(_CalculateFitnessAfterDemand_TEMPLATED))(p_subpop_fitnessEffect_callbacks, p_direct_effect_trait_indices); } if (model_type_ == SLiMModelType::kModelTypeWF) @@ -1506,7 +1504,7 @@ void Subpopulation::UpdateFitness(std::vector &p_mutationEffect } template -void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) +void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) { // manage the shuffle buffer; this is not quite as fast as templatizing this flag, but it's simpler, and it // only adds overhead when fitnessEffect() callbacks are present, otherwise it get optimized out completely @@ -1581,30 +1579,30 @@ void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect species_.ReturnShuffleBuffer(); } -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); // WF only: void Subpopulation::UpdateWFFitnessBuffers(void) @@ -1783,7 +1781,14 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati for (SLiMEidosBlock *mutationEffect_callback : p_mutationEffect_callbacks) { - // FIXME MULTITRAIT: I think this check will no longer be necessary, once my overhaul of optimization flags is complete; p_mutationEffect_callbacks will only contain active callbacks in the first place! +#if DEBUG + // It should now be the case that p_fitnessEffect_callbacks only contains active callbacks; the caller should guarantee this. + // This is made possible by the SetInsideTraitOrFitnessCalculation() mechanism, which locks out changes to fitnessEffect() + // and mutationEffect() callbacks inside trait/fitness calculation; the callback set is therefore fixed and can be cached. + if (!mutationEffect_callback->block_active_) + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): (internal error) mutationEffect() callback with block_active_ == false included in p_mutationEffect_callbacks." << EidosTerminate(mutationEffect_callback->identifier_token_); +#endif + if (mutationEffect_callback->block_active_) { slim_objectid_t callback_mutation_type_id = mutationEffect_callback->mutation_type_id_; @@ -1960,8 +1965,14 @@ slim_fitness_t Subpopulation::ApplyFitnessEffectCallbacks(std::vectorblock_active_) +#if DEBUG + // It should now be the case that p_fitnessEffect_callbacks only contains active callbacks; the caller should guarantee this. + // This is made possible by the SetInsideTraitOrFitnessCalculation() mechanism, which locks out changes to fitnessEffect() + // and mutationEffect() callbacks inside trait/fitness calculation; the callback set is therefore fixed and can be cached. + if (!fitnessEffect_callback->block_active_) + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyFitnessEffectCallbacks): (internal error) fitnessEffect() callback with block_active_ == false included in p_fitnessEffect_callbacks." << EidosTerminate(fitnessEffect_callback->identifier_token_); +#endif + { #if DEBUG_POINTS_ENABLED // SLiMgui debugging point diff --git a/core/subpopulation.h b/core/subpopulation.h index 4df54491..a3ed1249 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -160,9 +160,9 @@ class Subpopulation : public EidosDictionaryUnretained std::vector registered_mutation_callbacks_; // NOT OWNED: valid only during EvolveSubpopulation; callbacks used when this subpop is parental std::vector registered_reproduction_callbacks_; // nonWF only; NOT OWNED: valid only during EvolveSubpopulation; callbacks used when this subpop is parental - // These per-subpopulation caches are used by IndividualClass::DemandPhenotype() and are valid only within - // that method. There is a std::vector of PerTraitSubpopCache structs with one entry per trait in the - // species. When not in use, that vector should still have one entry per trait, with empty/nullptr values. + // These per-subpopulation caches are used by IndividualClass::DemandPhenotype_INDIVIDUALS() and are valid + // only within that method. There is a std::vector of PerTraitSubpopCache structs below with one entry per + // trait. When not in use, that vector should still have one entry per trait, with empty/nullptr values. typedef struct _PerTraitSubpopCaches { std::vector mutationEffect_callbacks_per_trait; // NOT OWNED: mutationEffect() callbacks per subpopulation per trait void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; @@ -383,11 +383,17 @@ class Subpopulation : public EidosDictionaryUnretained void FixNonNeutralCaches_OMP(void); #endif - void UpdateFitness(std::vector &p_mutationEffect_callbacks, std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation); + // this is called by Population::RecalculateFitness(); first it expresses demand for traits that have direct fitness effects, then it recalculates fitness values + void UpdateFitness(std::vector &p_subpop_mutationEffect_callbacks, std::vector &p_subpop_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation); + // this is called only by UpdateFitness(), and calculates fitness values given that trait values have already been demanded/calculated template - void _UpdateFitness(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); + void _CalculateFitnessAfterDemand(std::vector &p_subpop_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); + // WF only: this updates the WF model fitness buffers after UpdateFitness() has completed, preparing to draw parents according to relative fitness + void UpdateWFFitnessBuffers(void); + + // applying mutationEffect() and fitnessEffect() callbacks during trait/fitness calculation slim_effect_t ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual); slim_fitness_t ApplyFitnessEffectCallbacks(std::vector &p_fitnessEffect_callbacks, Individual *p_individual); @@ -430,7 +436,6 @@ class Subpopulation : public EidosDictionaryUnretained // WF only: void WipeIndividualsAndHaplosomes(std::vector &p_individuals, slim_popsize_t p_individual_count, slim_popsize_t p_first_male); void GenerateChildrenToFitWF(void); // given the set subpop size and sex ratio, configure the child generation haplosomes and individuals to fit - void UpdateWFFitnessBuffers(void); // update the WF model fitness buffers after UpdateFitness() void TallyLifetimeReproductiveOutput(void); void SwapChildAndParentHaplosomes(void); // switch to the next generation by swapping; the children become the parents From 75d7594746addb213be8a1f73f878351cfd7c93b Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 10 Jan 2026 11:14:33 -0600 Subject: [PATCH 073/107] add a bunch of multitrait debug test code --- core/individual.cpp | 749 ++++++++++++++++++++++++++++++++---- core/individual.h | 7 + core/slim_globals.cpp | 26 ++ core/slim_globals.h | 7 + core/slim_test_genetics.cpp | 114 ++++++ core/species.cpp | 4 +- core/species_eidos.cpp | 25 +- core/subpopulation.cpp | 16 + core/trait.cpp | 4 +- 9 files changed, 868 insertions(+), 84 deletions(-) diff --git a/core/individual.cpp b/core/individual.cpp index 0be4f60c..e7a42f58 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -1282,8 +1282,6 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) if (allowNullHaplosomes || !haplosome->IsNull()) vec->push_object_element_no_check_NORR(haplosome); - - haplosome_index += 2; break; } // chromosomes that have one haplosome, from the female parent @@ -1296,8 +1294,6 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) if (allowNullHaplosomes || !haplosome->IsNull()) vec->push_object_element_no_check_NORR(haplosome); - - haplosome_index += 1; break; } // chromosomes that have one haplosome, from the male parent @@ -1305,10 +1301,10 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) case ChromosomeType::kHM_HaploidMaleInherited: case ChromosomeType::kML_HaploidMaleLine: { - haplosome_index += 1; break; } } + haplosome_index += chromosome->IntrinsicPloidy(); } return EidosValue_SP(vec); @@ -1342,8 +1338,6 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) if (allowNullHaplosomes || !haplosome->IsNull()) vec->push_object_element_no_check_NORR(haplosome); - - haplosome_index += 2; break; } // chromosomes that have one haplosome, from the female parent @@ -1352,7 +1346,6 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) case ChromosomeType::kFL_HaploidFemaleLine: case ChromosomeType::kH_HaploidAutosome: // assumed to follow the same pattern, so skipped { - haplosome_index += 1; break; } // chromosomes that have one haplosome, from the male parent @@ -1364,11 +1357,10 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) if (allowNullHaplosomes || !haplosome->IsNull()) vec->push_object_element_no_check_NORR(haplosome); - - haplosome_index += 1; break; } } + haplosome_index += chromosome->IntrinsicPloidy(); } return EidosValue_SP(vec); @@ -6406,6 +6398,13 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotypeForIndividuals(Eido // forceRecalc eidos_logical_t forceRecalc = forceRecalc_value->LogicalAtIndex_NOCAST(0, nullptr); +#if DEBUG_TRAIT_DEMAND + std::cout << "# " << community.Tick() << " --- demandPhenotypeForIndividuals(): for traits {"; + for (slim_trait_index_t trait_index : trait_indices) + std::cout << " " << species->Traits()[trait_index]->Name(); + std::cout << " } in " << individuals_count << " individuals, forceRecalc == " << (forceRecalc ? "T" : "F") << std::endl; +#endif + // validate non-neutral caches and independent-dominance precalculated values // FIXME MULTITRAIT: VALIDATE NON-NEUTRAL CACHES HERE @@ -6705,6 +6704,10 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual { slim_trait_index_t trait_index = trait_indices[trait_indices_index]; +#if DEBUG_TRAIT_DEMAND + std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->symbol_ << "'" << std::endl; +#endif + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { Individual *ind = individuals_buffer[individual_index]; @@ -6788,8 +6791,6 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual } } } - - haplosome_index += 2; break; } @@ -6801,34 +6802,6 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual case ChromosomeType::kFL_HaploidFemaleLine: case ChromosomeType::kHM_HaploidMaleInherited: case ChromosomeType::kML_HaploidMaleLine: - { - for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) - { - slim_trait_index_t trait_index = trait_indices[trait_indices_index]; - - for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - { - Individual *ind = individuals_buffer[individual_index]; - Haplosome *haplosome = ind->haplosomes_[haplosome_index]; - - if (haplosome->IsNull()) - continue; - if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) - continue; - - Subpopulation *subpop = ind->subpopulation_; - Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; - std::vector &subpop_trait_mutationEffect_callbacks = subpop_trait_caches.mutationEffect_callbacks_per_trait; - auto IncorporateEffects_Haploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED; - - (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_trait_mutationEffect_callbacks); - } - } - - haplosome_index += 1; - break; - } - // haploid special cases that have an accompanying null haplosome for backward compatibility case ChromosomeType::kHNull_HaploidAutosomeWithNull: case ChromosomeType::kNullY_YSexChromosomeWithNull: @@ -6837,6 +6810,10 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual { slim_trait_index_t trait_index = trait_indices[trait_indices_index]; +#if DEBUG_TRAIT_DEMAND + std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->symbol_ << "'" << std::endl; +#endif + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { Individual *ind = individuals_buffer[individual_index]; @@ -6855,14 +6832,14 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_trait_mutationEffect_callbacks); } } - - haplosome_index += 2; break; } } if (species->DoingAnyMutationRunExperiments()) chromosome->StopMutationRunExperimentClock("DemandPhenotype_INDIVIDUALS()"); + + haplosome_index += chromosome->IntrinsicPloidy(); } // clear out each subpopulation's per-trait caches that we set up above; these are only for our private use @@ -6881,6 +6858,32 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED = nullptr; } } + +#if DEBUG + // Do a check of all computed results, against the same things computed by brute force. This will + // probably be quite expensive, so probably I can't leave it enabled all the time even in DEBUG. + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + slim_effect_t calculated_phenotype = ind->trait_info_[trait_index].phenotype_; + slim_effect_t check_phenotype = ind->_CheckPhenotypeForTrait(trait_index); + + // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about + // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait + // calculations, so I use a larger threshold here of 1e-6; we'll see how this does in practice. + if (std::abs(calculated_phenotype - check_phenotype) > (slim_effect_t)1e-6) + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) phenotype check failed (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); + } + } +#endif } template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices); @@ -6935,6 +6938,10 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s TraitType traitType = trait->Type(); slim_effect_t trait_baseline_offset = trait->BaselineOffset(); +#if DEBUG_TRAIT_DEMAND + std::cout << " DemandPhenotype_SUBPOP() trait " << trait->Name() << " (" << traitType << ") has baseline offset " << trait_baseline_offset << std::endl; +#endif + if (traitType == TraitType::kAdditive) { for (int individual_index = 0; individual_index < individuals_count; ++individual_index) @@ -6942,6 +6949,10 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s Individual *ind = individuals_buffer[individual_index]; IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; +#if DEBUG_TRAIT_DEMAND + //std::cout << " individual #" << individual_index << " offset " << trait_info.offset_ << std::endl; +#endif + if (f_force_recalc) { trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; @@ -6961,6 +6972,10 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s Individual *ind = individuals_buffer[individual_index]; IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; +#if DEBUG_TRAIT_DEMAND + //std::cout << " individual #" << individual_index << " offset " << trait_info.offset_ << std::endl; +#endif + if (f_force_recalc) { trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; @@ -7074,6 +7089,10 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s } } +#if DEBUG_TRAIT_DEMAND + std::cout << " DemandPhenotype_SUBPOP() calculating trait " << trait->Name() << " for chromosome '" << chromosome->symbol_ << "'" << std::endl; +#endif + // Then process the chromosome for the focal trait switch (chromosome->Type()) { @@ -7112,11 +7131,15 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s else { // diploid, both haplosomes non-null +#if DEBUG_TRAIT_DEMAND + //std::cout << " individual #" << individual_index << " existing trait value == " << ind->trait_info_[trait_index].phenotype_ << std::endl; +#endif (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait_index, subpop_per_trait_mutationEffect_callbacks); +#if DEBUG_TRAIT_DEMAND + //std::cout << " individual #" << individual_index << " updated trait value == " << ind->trait_info_[trait_index].phenotype_ << std::endl; +#endif } } - - haplosome_index += 2; break; } @@ -7128,24 +7151,6 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s case ChromosomeType::kFL_HaploidFemaleLine: case ChromosomeType::kHM_HaploidMaleInherited: case ChromosomeType::kML_HaploidMaleLine: - { - for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - { - Individual *ind = individuals_buffer[individual_index]; - Haplosome *haplosome = ind->haplosomes_[haplosome_index]; - - if (haplosome->IsNull()) - continue; - if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) - continue; - - (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_per_trait_mutationEffect_callbacks); - } - - haplosome_index += 1; - break; - } - // haploid special cases that have an accompanying null haplosome for backward compatibility case ChromosomeType::kHNull_HaploidAutosomeWithNull: case ChromosomeType::kNullY_YSexChromosomeWithNull: @@ -7162,8 +7167,6 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_per_trait_mutationEffect_callbacks); } - - haplosome_index += 2; break; } } @@ -7171,7 +7174,35 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s if (species->DoingAnyMutationRunExperiments()) chromosome->StopMutationRunExperimentClock("DemandPhenotype_SUBPOP()"); + + haplosome_index += chromosome->IntrinsicPloidy(); + } + +#if DEBUG + // Do a check of all computed results, against the same things computed by brute force. This will + // probably be quite expensive, so probably I can't leave it enabled all the time even in DEBUG. + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + slim_effect_t calculated_phenotype = ind->trait_info_[trait_index].phenotype_; + slim_effect_t check_phenotype = ind->_CheckPhenotypeForTrait(trait_index); + + // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about + // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait + // calculations, so I use a larger threshold here of 1e-6; we'll see how this does in practice. + if (std::abs(calculated_phenotype - check_phenotype) > (slim_effect_t)1e-6) + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) phenotype check failed (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); + } } +#endif } template void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector p_mutationEffect_callbacks); @@ -7279,7 +7310,7 @@ template void Individual::_IncorporateEffects_Haploid(Sp // Low-level method to calculate a phenotype for one individual, for one diploid chromosome, for one trait. // This will put the result of the calculation into the individual's phenotype information. This is called // by Individual_Class::DemandPhenotype_X(), which loops over chromosomes, traits, and individuals. -template +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) { #if DEBUG @@ -7345,7 +7376,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos Mutation *mutation = mut_block_ptr + haplosome1_mutindex; slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; - if (f_additive_trait) + if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); @@ -7380,7 +7411,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos Mutation *mutation = mut_block_ptr + haplosome2_mutindex; slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; - if (f_additive_trait) + if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); @@ -7429,7 +7460,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos Mutation *mutation = mut_block_ptr + haplosome1_mutindex; slim_effect_t homozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].homozygous_effect_; - if (f_additive_trait) + if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); @@ -7461,7 +7492,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos Mutation *mutation = mut_block_ptr + haplosome1_mutindex; slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; - if (f_additive_trait) + if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); @@ -7516,7 +7547,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos Mutation *mutation = mut_block_ptr + haplosome2_mutindex; slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; - if (f_additive_trait) + if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); @@ -7568,7 +7599,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos Mutation *mutation = mut_block_ptr + haplosome1_mutindex; slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; - if (f_additive_trait) + if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); @@ -7598,7 +7629,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos Mutation *mutation = mut_block_ptr + haplosome2_mutindex; slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; - if (f_additive_trait) + if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); @@ -7633,6 +7664,580 @@ template void Individual::_IncorporateEffects_Diploid(Species template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +// the rest of the code below is for checking the correctness of the calculations performed by the code above + +slim_effect_t Individual::_CheckPhenotypeForTrait(slim_trait_index_t trait_index) +{ + Subpopulation *subpop = subpopulation_; + Species &species = subpop->species_; + Community &community = species.community_; + Trait *trait = species.Traits()[trait_index]; + TraitType traitType = trait->Type(); + int haplosome_index = 0; + slim_effect_t trait_value = trait->BaselineOffset(); + IndividualTraitInfo &trait_info = trait_info_[trait_index]; + + if (traitType == TraitType::kAdditive) + trait_value += trait_info.offset_; + else // (traitType == TraitType::kMultiplicative) + trait_value *= trait_info.offset_; + + // because the low-level functions are designed to combine their effects into our phenotype, + // we save the phenotype value here and restore it at the end so we leave it untouched + slim_effect_t saved_phenotype = trait_info.phenotype_; + trait_info.phenotype_ = trait_value; + + // determine the versions of _IncorporateEffects_X() we will use; note that we use special + // variants that do not use the non-neutral caches, since that is what we want to check! + std::vector subpop_per_trait_mutationEffect_callbacks = species.CallbackBlocksMatching(community.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, subpop->subpopulation_id_, trait_index, -1, /* p_active_only */ true); + + // then loop over the chromosomes and incorporate trait effects + for (Chromosome *chromosome : species.Chromosomes()) + { + switch (chromosome->Type()) + { + // diploid, possibly with one or both being null haplosomes + case ChromosomeType::kA_DiploidAutosome: + case ChromosomeType::kX_XSexChromosome: + case ChromosomeType::kZ_ZSexChromosome: + { + Haplosome *haplosome1 = haplosomes_[haplosome_index]; + Haplosome *haplosome2 = haplosomes_[haplosome_index+1]; + + if (haplosome1->IsNull()) + { + if (!haplosome2->IsNull()) + { + // hemizygous (haplosome2) + _Check_IncorporateEffects_Hemizygous(&species, haplosome2, trait, subpop_per_trait_mutationEffect_callbacks); + } + else + { + // both haplosomes are null (only happens with chromosome type "A"; no work to be done + } + } + else if (haplosome2->IsNull()) + { + // hemizygous (haplosome1) + _Check_IncorporateEffects_Hemizygous(&species, haplosome1, trait, subpop_per_trait_mutationEffect_callbacks); + } + else + { + // diploid, both haplosomes non-null + _Check_IncorporateEffects_Diploid(&species, haplosome1, haplosome2, trait, subpop_per_trait_mutationEffect_callbacks); + } + break; + } + + // haploid, possibly null + case ChromosomeType::kH_HaploidAutosome: + case ChromosomeType::kY_YSexChromosome: + case ChromosomeType::kW_WSexChromosome: + case ChromosomeType::kHF_HaploidFemaleInherited: + case ChromosomeType::kFL_HaploidFemaleLine: + case ChromosomeType::kHM_HaploidMaleInherited: + case ChromosomeType::kML_HaploidMaleLine: + // haploid special cases that have an accompanying null haplosome for backward compatibility + case ChromosomeType::kHNull_HaploidAutosomeWithNull: + case ChromosomeType::kNullY_YSexChromosomeWithNull: + { + Haplosome *haplosome = haplosomes_[haplosome_index + ((chromosome->Type() == ChromosomeType::kNullY_YSexChromosomeWithNull) ? 1 : 0)]; + + if (!haplosome->IsNull()) + _Check_IncorporateEffects_Haploid(&species, haplosome, trait, subpop_per_trait_mutationEffect_callbacks); + break; + } + } + + haplosome_index += chromosome->IntrinsicPloidy(); + } + + // finally, restore our saved phenotype so we don't modify the official individual state + trait_value = trait_info.phenotype_; + trait_info.phenotype_ = saved_phenotype; + + return trait_value; +} + +void Individual::_Check_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) +{ +#if DEBUG + // This method assumes that haplosome is not a null haplosome; the caller needs to guarantee this + if (haplosome->IsNull()) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) null haplosome." << EidosTerminate(); +#endif + + // set up variables for what are, in the main code path, templated variants + slim_trait_index_t trait_index = trait->Index(); + bool f_callbacks = (p_mutationEffect_callbacks.size() > 0); + bool f_singlecallback = (p_mutationEffect_callbacks.size() == 1); + bool f_additiveTrait = (trait->Type() == TraitType::kAdditive); + + // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast + MutationType *single_callback_mut_type = nullptr; + + if (f_singlecallback) + { + // our caller already did this lookup, to select this case, so this lookup is guaranteed to succeed + slim_objectid_t mutation_type_id = p_mutationEffect_callbacks[0]->mutation_type_id_; + + single_callback_mut_type = species->MutationTypeWithID(mutation_type_id); + } + + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + const int32_t mutrun_count = haplosome->mutrun_count_; + slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + for (int run_index = 0; run_index < mutrun_count; ++run_index) + { + const MutationRun *mutrun = haplosome->mutruns_[run_index]; + + // Read directly from the MutationRun buffers (we do not use non-neutral caches; we evaluate everything) + const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); + const MutationIndex *haplosome_max = mutrun->end_pointer_const(); + + // scan the mutation run and apply mutation effects + while (haplosome_iter != haplosome_max) + { + MutationIndex haplosome_mutation = *haplosome_iter++; + Mutation *mutation = (mut_block_ptr + haplosome_mutation); + slim_effect_t effect = mutation_block->TraitInfoForIndex(haplosome_mutation)[trait_index].homozygous_effect_; + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + + effect_accumulator += effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + + if (effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= effect; + } + } + } + + trait_info_[trait_index].phenotype_ = effect_accumulator; +} + +void Individual::_Check_IncorporateEffects_Hemizygous(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) +{ +#if DEBUG + // This method assumes that haplosome is not a null haplosome; the caller needs to guarantee this + if (haplosome->IsNull()) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) null haplosome." << EidosTerminate(); +#endif + + // set up variables for what are, in the main code path, templated variants + slim_trait_index_t trait_index = trait->Index(); + bool f_callbacks = (p_mutationEffect_callbacks.size() > 0); + bool f_singlecallback = (p_mutationEffect_callbacks.size() == 1); + bool f_additiveTrait = (trait->Type() == TraitType::kAdditive); + + // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast + MutationType *single_callback_mut_type = nullptr; + + if (f_singlecallback) + { + // our caller already did this lookup, to select this case, so this lookup is guaranteed to succeed + slim_objectid_t mutation_type_id = p_mutationEffect_callbacks[0]->mutation_type_id_; + + single_callback_mut_type = species->MutationTypeWithID(mutation_type_id); + } + + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + const int32_t mutrun_count = haplosome->mutrun_count_; + slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + for (int run_index = 0; run_index < mutrun_count; ++run_index) + { + const MutationRun *mutrun = haplosome->mutruns_[run_index]; + + // Read directly from the MutationRun buffers (we do not use non-neutral caches; we evaluate everything) + const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); + const MutationIndex *haplosome_max = mutrun->end_pointer_const(); + + // scan the mutation run and apply mutation effects + while (haplosome_iter != haplosome_max) + { + MutationIndex haplosome_mutation = *haplosome_iter++; + Mutation *mutation = (mut_block_ptr + haplosome_mutation); + slim_effect_t effect = mutation_block->TraitInfoForIndex(haplosome_mutation)[trait_index].hemizygous_effect_; + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + + effect_accumulator += effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + + if (effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= effect; + } + } + } + + trait_info_[trait_index].phenotype_ = effect_accumulator; +} + +void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks) +{ +#if DEBUG + // This method assumes that haplosome1 and haplosome2 are not null; the caller needs to guarantee this + if (haplosome1->IsNull() || haplosome2->IsNull()) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) null haplosome." << EidosTerminate(); +#endif + + // set up variables for what are, in the main code path, templated variants + slim_trait_index_t trait_index = trait->Index(); + bool f_callbacks = (p_mutationEffect_callbacks.size() > 0); + bool f_singlecallback = (p_mutationEffect_callbacks.size() == 1); + bool f_additiveTrait = (trait->Type() == TraitType::kAdditive); + + // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast + MutationType *single_callback_mut_type = nullptr; + + if (f_singlecallback) + { + // our caller already did this lookup, to select this case, so this lookup is guaranteed to succeed + slim_objectid_t mutation_type_id = p_mutationEffect_callbacks[0]->mutation_type_id_; + + single_callback_mut_type = species->MutationTypeWithID(mutation_type_id); + } + + MutationBlock *mutation_block = species->SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + const int32_t mutrun_count = haplosome1->mutrun_count_; + slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + for (int run_index = 0; run_index < mutrun_count; ++run_index) + { + const MutationRun *mutrun1 = haplosome1->mutruns_[run_index]; + const MutationRun *mutrun2 = haplosome2->mutruns_[run_index]; + + // Read directly from the MutationRun buffers (we do not use non-neutral caches; we evaluate everything) + const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); + const MutationIndex *haplosome2_iter = mutrun2->begin_pointer_const(); + + const MutationIndex *haplosome1_max = mutrun1->end_pointer_const(); + const MutationIndex *haplosome2_max = mutrun2->end_pointer_const(); + + // first, handle the situation before either haplosome iterator has reached the end of its haplosome, for simplicity/speed + if ((haplosome1_iter != haplosome1_max) && (haplosome2_iter != haplosome2_max)) + { + MutationIndex haplosome1_mutindex = *haplosome1_iter, haplosome2_mutindex = *haplosome2_iter; + slim_position_t haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_, haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + + do + { + if (haplosome1_iter_position < haplosome2_iter_position) + { + // Process a mutation in haplosome1 since it is leading + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + + if (++haplosome1_iter == haplosome1_max) + break; + else { + haplosome1_mutindex = *haplosome1_iter; + haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; + } + } + else if (haplosome1_iter_position > haplosome2_iter_position) + { + // Process a mutation in haplosome2 since it is leading + Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + + if (++haplosome2_iter == haplosome2_max) + break; + else { + haplosome2_mutindex = *haplosome2_iter; + haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + } + } + else + { + // Look for homozygosity: haplosome1_iter_position == haplosome2_iter_position + slim_position_t position = haplosome1_iter_position; + const MutationIndex *haplosome1_start = haplosome1_iter; + + // advance through haplosome1 as long as we remain at the same position, handling one mutation at a time + do + { + const MutationIndex *haplosome2_matchscan = haplosome2_iter; + + // advance through haplosome2 with haplosome2_matchscan, looking for a match for the current mutation in haplosome1, to determine whether we are homozygous or not + while (haplosome2_matchscan != haplosome2_max && (mut_block_ptr + *haplosome2_matchscan)->position_ == position) + { + if (haplosome1_mutindex == *haplosome2_matchscan) + { + // a match was found, so we multiply our fitness by the full homozygous effect + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t homozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].homozygous_effect_; + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += homozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (homozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= homozygous_effect; + } + goto homozygousExit1; + } + + haplosome2_matchscan++; + } + + // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient + { + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + + homozygousExit1: + + if (++haplosome1_iter == haplosome1_max) + break; + else { + haplosome1_mutindex = *haplosome1_iter; + haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; + } + } while (haplosome1_iter_position == position); + + // advance through haplosome2 as long as we remain at the same position, handling one mutation at a time + do + { + const MutationIndex *haplosome1_matchscan = haplosome1_start; + + // advance through haplosome1 with haplosome1_matchscan, looking for a match for the current mutation in haplosome2, to determine whether we are homozygous or not + while ((haplosome1_matchscan != haplosome1_max) && ((mut_block_ptr + *haplosome1_matchscan)->position_ == position)) + { + if (haplosome2_mutindex == *haplosome1_matchscan) + { + // a match was found; we know this match was already found by the haplosome1 loop above, so our fitness has already been multiplied appropriately + goto homozygousExit2; + } + + haplosome1_matchscan++; + } + + // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient + { + Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + + homozygousExit2: + + if (++haplosome2_iter == haplosome2_max) + break; + else { + haplosome2_mutindex = *haplosome2_iter; + haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + } + } while (haplosome2_iter_position == position); + + // break out if either haplosome has reached its end + if (haplosome1_iter == haplosome1_max || haplosome2_iter == haplosome2_max) + break; + } + } while (true); + } + + // one or the other haplosome has now reached its end, so now we just need to handle the remaining mutations in the unfinished haplosome +#if DEBUG + assert(!(haplosome1_iter != haplosome1_max && haplosome2_iter != haplosome2_max)); +#endif + + // if haplosome1 is unfinished, finish it + while (haplosome1_iter != haplosome1_max) + { + MutationIndex haplosome1_mutindex = *haplosome1_iter++; + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + + // if haplosome2 is unfinished, finish it + while (haplosome2_iter != haplosome2_max) + { + MutationIndex haplosome2_mutindex = *haplosome2_iter++; + Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; + + if (f_additiveTrait) + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + effect_accumulator += heterozygous_effect; + } + else // multiplicative + { + if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) + { + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + + if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + } + + trait_info_[trait_index].phenotype_ = effect_accumulator; +} diff --git a/core/individual.h b/core/individual.h index 5e973722..eac1b853 100644 --- a/core/individual.h +++ b/core/individual.h @@ -417,6 +417,13 @@ class Individual : public EidosDictionaryUnretained template void _IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); + // Debugging checkback for phenotype calculation; this is very slow, and does not use the non-neutral cache + slim_effect_t _CheckPhenotypeForTrait(slim_trait_index_t trait_index); + + void _Check_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); + void _Check_IncorporateEffects_Hemizygous(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); + void _Check_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); + // for Subpopulation::ExecuteMethod_takeMigrants() friend Subpopulation; }; diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 0b65c01c..94480ce8 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -653,6 +653,30 @@ std::string StringForSLiMCycleStage(SLiMCycleStage p_stage) } // stream output for enumerations +std::string StringForTraitType(TraitType p_trait_type) +{ + switch (p_trait_type) + { + case TraitType::kAdditive: return gStr_additive; + case TraitType::kMultiplicative: return gStr_multiplicative; + } + EIDOS_TERMINATION << "ERROR (StringForTraitType): (internal error) unexpected p_trait_type value." << EidosTerminate(); +} + +TraitType TraitTypeForString(std::string type) +{ + if (type == gStr_additive) return TraitType::kAdditive; + else if (type == gStr_multiplicative) return TraitType::kMultiplicative; + else + EIDOS_TERMINATION << "ERROR (TraitTypeForString): unrecognized triat type '" << type << "'." << EidosTerminate(); +} + +std::ostream& operator<<(std::ostream& p_out, TraitType p_trait_type) +{ + p_out << StringForTraitType(p_trait_type); + return p_out; +} + std::string StringForChromosomeType(ChromosomeType p_chromosome_type) { switch (p_chromosome_type) @@ -1629,6 +1653,8 @@ const std::string &gStr_willAutolog = EidosRegisteredString("willAutolog", gID_w const std::string &gStr_context = EidosRegisteredString("context", gID_context); // mostly other fixed strings +const std::string gStr_additive = "additive"; // these trait type strings are not registered, no need +const std::string gStr_multiplicative = "multiplicative"; const std::string gStr_A = "A"; // these nucleotide strings are not registered, no need const std::string gStr_C = "C"; const std::string gStr_G = "G"; diff --git a/core/slim_globals.h b/core/slim_globals.h index a46f993d..7be2db0e 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -492,6 +492,7 @@ void AccumulateMemoryUsageIntoTotal_Community(SLiMMemoryUsage_Community &p_usage #define DEBUG_BLOCK_REG_DEREG 0 // turn on to get logging about script block registration/deregistration #define DEBUG_SHUFFLE_BUFFER 1 // debug memory overruns with the shuffle buffer #define DEBUG_TICK_RANGES 0 // debug tick range parsing and evaluation +#define DEBUG_TRAIT_DEMAND 0 // enable debugging logs about the trait "demand" evalutation process #define DEBUG_LESS_INTENSIVE 0 // decrease the frequency of some very intensive DEBUG checks @@ -581,6 +582,10 @@ enum class TraitType : uint8_t { kAdditive }; +std::string StringForTraitType(TraitType p_trait_type); +TraitType TraitTypeForString(std::string type); // raises if no match +std::ostream& operator<<(std::ostream& p_out, TraitType p_trait_type); + // This enumeration represents the type of a chromosome. Note that the sex of an individual cannot always be inferred // from chromosomal state, and the user is allowed to play games with null haplosomes; the chromosomes follow the sex // 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; extern const std::string &gStr_willAutolog; extern const std::string &gStr_context; +extern const std::string gStr_additive; // these trait type strings are not registered, no need +extern const std::string gStr_multiplicative; extern const std::string gStr_A; // these nucleotide strings are not registered, no need extern const std::string gStr_C; extern const std::string gStr_G; diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index ae5b588b..d9152436 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1593,6 +1593,120 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(test_zygosity4); + + // Complex multi-trait, multi-chrom models that will (hopefully) exercise all the different code paths for + // phenotype evaluation -- callbacks, different DES combinations, neutral and non-neutral, etc. + // The intention here is that these models don't test themselves; they are stochastic and there is no + // expectation regarding their outcome. Instead, _CheckPhenotypeForTrait() cross-checks them under DEBUG. + + std::string complex_multi_1 = // this is an abbreviated version of test script complex_multi_test_1.slim + R"V0G0N( +initialize() { + defineConstant("I1", 5.0); + defineConstant("I2", -5.0); + defineConstant("OPT1", 10.0); + defineConstant("OPT2", 10.0); + defineConstant("SD1", 2.0); + defineConstant("SD2", 2.0); + + initializeSex(); + + // multiplicative traits + popgen1T = initializeTrait("popgen1T", "mul", 1.01, 1.0, 0.01, directFitnessEffect=T); // will have a mix of dominance + popgen2T = initializeTrait("popgen2T", "mul", 1.01, 1.0, 0.01, directFitnessEffect=T); // will be independent dominance + n1T = initializeTrait("n1T", "mul", NULL, NULL, NULL, directFitnessEffect=T); // neutral with direct effect + n2T = initializeTrait("n2T", "mul", NULL, NULL, NULL, directFitnessEffect=F); // neutral with no direct effect + + // additive traits + quant1T = initializeTrait("quant1T", "add", I1, 0.0, 0.01, directFitnessEffect=F); // will have a mix of dominance + quant2T = initializeTrait("quant2T", "add", I2, 0.0, 0.01, directFitnessEffect=F); // will be independent dominance + n3T = initializeTrait("n3T", "add", NULL, NULL, NULL, directFitnessEffect=F); // non-neutral with no direct effect + + // quant1T / quant2T will be demanded in script; popgen1T / popgen2T / n1T will be demanded because they have direct effects + // calculation of popgen2T and quant2T should be extremely efficient since they are independent dominance + // calculation of n1T should be omitted entirely; SLiM should detect that it is neutral, and not even set phenotype values + // n2T and n3T should not be demanded, and should thus never be calculated by SLiM, which we can check in script + + // mutation types + initializeMutationType("m1", 0.4, "f", 0.0); // neutral for all traits + + initializeMutationType("m2", 0.4, "e", 0.001); // beneficial for the popgen traits + m2.setEffectDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits + m2.setEffectDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits + + initializeMutationType("m3", 0.4, "g", -0.001, 1.0); // deleterious for the popgen traits + m3.setEffectDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits + m3.setEffectDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits + + c(m2,m3).setEffectDistributionForTrait(n3T, "n", -5.0, 0.5); // very biased and wide DES for n3T + + // set up independent dominance for popgen2T and quant2T; note that setting this for m1 should be unnecessary (it is neutral) + c(m1,m2,m3).setDefaultDominanceForTrait(c(popgen2T, quant2T), NAN); + + // prevent converting to substitutions for m2 and m3; once shifting the baseline offset is supported, this will not be needed + c(m2,m3).convertToSubstitution = F; + + initializeGenomicElementType("g1", m1, 1.0); // neutral + initializeGenomicElementType("g2", 1:3, c(2, 1, 1)); // mixture + + ids = 1:5; + symbols = c(1, 2, "X", "Y", "MT"); + lengths = rdunif(5, 1e7, 2e7); + types = c("A", "A", "X", "Y", "H"); + names = c("A1", "A2", "X", "Y", "MT"); + + for (id in ids, symbol in symbols, length in lengths, type in types, name in names) + { + initializeChromosome(id, length, type, symbol, name); + initializeMutationRate(1e-7); + initializeRecombinationRate(1e-8); + + if (id == 1) + initializeGenomicElement(g1); // autosome 1 is pure neutral, using only m1 + else + initializeGenomicElement(g2); // autosome 2 is a mix, using m1 / m2 / m3 + } +} + +// set random dominance effects for the popgen1T and quant1T traits +// other effects are generated as specified by the mutation type DES +mutation(m2) { + mut.popgen1TDominance = runif(1); + mut.quant1TDominance = runif(1); + return T; +} +mutation(m3) { + mut.popgen1TDominance = runif(1); + mut.quant1TDominance = runif(1); + return T; +} + +1 late() { + sim.addSubpop("p1", 20); +} + +1: late() { + inds = sim.subpopulations.individuals; + sim.demandPhenotype(NULL, c(sim.quant1T, sim.quant2T)); + fitnessEffect_q1 = dnorm(inds.quant1T, OPT1, SD1) / dnorm(0.0, 0.0, SD1); + fitnessEffect_q2 = dnorm(inds.quant2T, OPT2, SD2) / dnorm(0.0, 0.0, SD2); + inds.fitnessScaling = fitnessEffect_q1 * fitnessEffect_q2; +} + +2: first() { + inds = sim.subpopulations.individuals; + + // check that traits that do not require calculation remain uncalculated + //if (!all(isNAN(inds.n1T))) stop("n1T was calculated unnecessarily"); // the smarts for this are not yet implemented! + if (!all(isNAN(inds.n2T))) stop("n2T was calculated unnecessarily"); + if (!all(isNAN(inds.n3T))) stop("n3T was calculated unnecessarily"); +} + +50 late() { } + )V0G0N"; + + SLiMAssertScriptSuccess(complex_multi_1); + std::cout << "_RunMultitraitTests() done" << std::endl; } diff --git a/core/species.cpp b/core/species.cpp index 174afba8..a60c440e 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -392,7 +392,7 @@ void Species::CheckOptimizationFlags(void) slim_trait_index_t trait_index = trait->Index(); MutationTraitInfo &trait_info = mut_trait_info[trait_index]; - if (trait_info.effect_size_ != 0.0) + if (trait_info.effect_size_ != (slim_effect_t)0.0) { // this mutation is non-neutral for this trait if (trait->trait_all_neutral_mutations_ != false) @@ -427,7 +427,7 @@ void Species::_NoteNonNeutralMutation(const Mutation *p_mut) slim_trait_index_t trait_index = trait->Index(); MutationTraitInfo &trait_info = mut_trait_info[trait_index]; - if (trait_info.effect_size_ != 0.0) + if (trait_info.effect_size_ != (slim_effect_t)0.0) { // this mutation is non-neutral for this trait trait->trait_all_neutral_mutations_ = false; diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 3fea7ba6..ecba9519 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1678,9 +1678,9 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string std::string type_string = type_value->StringAtIndex_NOCAST(0, nullptr); TraitType type; - if ((type_string == "multiplicative") || (type_string == "mul")) + if ((type_string == gStr_multiplicative) || (type_string == "mul")) type = TraitType::kMultiplicative; - else if ((type_string == "additive") || (type_string == "add")) + else if ((type_string == gStr_additive) || (type_string == "add")) type = TraitType::kAdditive; else 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 eidos_logical_t forceRecalc = forceRecalc_value->LogicalAtIndex_NOCAST(0, nullptr); // subpops - std::vector subpops_to_demand; + THREAD_SAFETY_IN_ACTIVE_PARALLEL("Species::ExecuteMethod_demandPhenotype(): usage of statics"); + + static std::vector subpops_to_demand; // using and clearing a static prevents allocation thrash; should be safe from re-entry + subpops_to_demand.resize(0); if (subpops_value->Type() == EidosValueType::kValueNULL) { // demand the specified phenotypes across all subpopulations - subpops_to_demand.resize(population_.subpops_.size()); - for (const std::pair &subpop_pair : population_.subpops_) subpops_to_demand.push_back(subpop_pair.second); } @@ -3134,10 +3135,8 @@ EidosValue_SP Species::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_metho if (requested_subpop_count) { - subpops_to_demand.resize(requested_subpop_count); - for (int requested_subpop_index = 0; requested_subpop_index < requested_subpop_count; ++requested_subpop_index) - subpops_to_demand.emplace_back(SLiM_ExtractSubpopulationFromEidosValue_io(subpops_value, requested_subpop_index, &community_, this, "demandPhenotype()")); // SPECIES CONSISTENCY CHECK + subpops_to_demand.push_back(SLiM_ExtractSubpopulationFromEidosValue_io(subpops_value, requested_subpop_index, &community_, this, "demandPhenotype()")); // SPECIES CONSISTENCY CHECK // unique subpops_to_demand to avoid duplicated work std::sort(subpops_to_demand.begin(), subpops_to_demand.end()); @@ -3145,6 +3144,16 @@ EidosValue_SP Species::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_metho } } +#if DEBUG_TRAIT_DEMAND + std::cout << "# " << community_.Tick() << " --- demandPhenotype(): for traits {"; + for (slim_trait_index_t trait_index : trait_indices) + std::cout << " " << Traits()[trait_index]->Name(); + std::cout << " } in subpops {"; + for (Subpopulation *subpop : subpops_to_demand) + std::cout << " p" << subpop->subpopulation_id_; + std::cout << " }, forceRecalc == " << (forceRecalc ? "T" : "F") << std::endl; +#endif + // validate non-neutral caches and independent-dominance precalculated values // FIXME MULTITRAIT: VALIDATE NON-NEUTRAL CACHES HERE diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index f488221b..9823c5cd 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1387,6 +1387,9 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio { // we know this subpopulation has effectively constant fitness; we therefore don't express demand for // any traits, which means trait may keep NAN values even if the traits have a direct fitness effect +#if DEBUG_TRAIT_DEMAND + std::cout << "# " << community_.Tick() << " --- UpdateFitness() determined constant fitness of " << constant_fitness_value << std::endl; +#endif if (model_type_ == SLiMModelType::kModelTypeWF) { @@ -1425,11 +1428,24 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio // demand phenotypes for all the relevant traits if (p_direct_effect_trait_indices.size()) { +#if DEBUG_TRAIT_DEMAND + std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding traits {"; + for (slim_trait_index_t trait_index : p_direct_effect_trait_indices) + std::cout << " " << species_.Traits()[trait_index]->Name(); + std::cout << " } in subpop p" << subpopulation_id_ << ", forceRecalc == " << (p_force_trait_recalculation ? "T" : "F") << std::endl; +#endif + if (p_force_trait_recalculation) Individual_Class::DemandPhenotype_SUBPOP(&species_, this, p_direct_effect_trait_indices, p_subpop_mutationEffect_callbacks); else Individual_Class::DemandPhenotype_SUBPOP(&species_, this, p_direct_effect_trait_indices, p_subpop_mutationEffect_callbacks); } + else + { +#if DEBUG_TRAIT_DEMAND + std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding NO traits in subpop p" << subpopulation_id_ << std::endl; +#endif + } // then loop over individuals and pull together the relevant phenotype values, fitnessEffect() callbacks, // subpopulation fitnessScaling, and individual fitnessScaling to produce final individual fitness values; diff --git a/core/trait.cpp b/core/trait.cpp index b19c46eb..6e6d3dba 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -113,8 +113,8 @@ EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) { if (!static_type_string_multiplicative) { - static_type_string_multiplicative = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("multiplicative")); - static_type_string_additive = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("additive")); + static_type_string_multiplicative = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_multiplicative)); + static_type_string_additive = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_additive)); } } From f42fb49326617dbbd8551a4ad6791acf29be5fd8 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 11 Jan 2026 18:50:31 -0600 Subject: [PATCH 074/107] fix #607, add logging of new-mutation info --- QtSLiM/help/SLiMHelpClasses.html | 9 + SLiMgui/SLiMHelpClasses.rtf | 172 +++++++ VERSIONS | 4 + core/haplosome.cpp | 6 +- core/individual.cpp | 2 +- core/mutation_type.cpp | 566 +++++++++++++++++++++- core/mutation_type.h | 49 +- core/population.cpp | 28 +- core/population.h | 2 +- core/slim_globals.cpp | 2 + core/slim_globals.h | 4 + core/species.cpp | 6 +- eidos/eidos_functions_stats.cpp | 30 +- eidos/eidos_globals.h | 24 + eidos/eidos_test_functions_statistics.cpp | 6 +- 15 files changed, 867 insertions(+), 43 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 979fe1f6..0f70526d 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -800,6 +800,15 @@

Returns the parameters that configure the distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DES type "s", and type float for all other DES types.

– (string)effectDistributionTypeForTrait([Niso<Trait> trait = NULL])

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

+

– (fo<DataFrame>)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

+

Returns mutation data produced by the mutation type’s logging facility, as configured by logMutationData().  The data returned can be in the form of means across all logged mutations (for kind="mean"), standard deviations across all logged mutations (for kind="sd"), or separate values for each mutation (for kind="values").  If logging only of means was enabled (with the meanOnly=T option to logMutationData()), only kind="mean" is allowed, since separate values for each mutation are then not logged.

+

The remaining flags control which columns of data should be returned; see logMutationData() for a summary of the mutation properties they refer to.  If only one data column is specified by the flags, a vector of values (or a mean or standard deviation) will be returned for that one specified data column.  If more than one flag is set to T, a DataFrame object will be returned with named columns of values (or means, or standard deviations) for each specified data column.  The trait property specifies which traits values should be returned for, with respect to the effect, dominance, and hemizygousDominance flags; see logMutationData() for further description.  If all of these flags are F (the default), that is taken to mean that all logged data should be returned; in that case, a vector will be returned if only one column of data was logged, otherwise a DataFrame object will be returned, the same as when flags are specified explicitly.  Flags set to T for data columns that were not actually logged will simply be ignored; similarly, traits specified by trait that were not actually logged will simply be ignored.  See the Eidos manual for the DataFrame class documentation.

+

– (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

+

Starts or ends logging of data about new mutations belonging to the target mutation type.  If autogeneratedOnly is T (the default), only new mutations generated automatically by SLiM will be logged (including mutations that are substituted in for an auto-generated mutation using a mutation() callback; that is still considered part of the auto-generation process).  If autogeneratedOnly is F, mutations generated in script, such as with addNewMutation(), addNewDrawnMutation(), and reading from files such as VCF, MS, or .trees, will also be logged.  The logged information can be obtained later with the loggedData() method.  Once logging has been started with enable=T it cannot be modified, only stopped with enable=F; and if logging is subsequently resumed with enable=T, any previously logged data will be discarded.  (This can be useful if you wish to limit the size of the in-memory data while continuing to log new data: periodically write the accumulated data to a file and then disable and re-enable logging to discard the old data.)

+

If meanOnly is F (the default), values for each new mutation will be kept separately.  Beware: the memory usage entailed by this option can be extremely large!  Alternatively, if meanOnly is T, only a running sum, used to compute a mean, will be kept for each type of data; the memory usage for this option will be small and constant, but of course a mean is more useful for some columns of data than others.  If per-mutation data is desired for any one column, use meanOnly=F; this option cannot be controlled independently for the various columns of data being logged.

+

Next are parameters that control which mutation properties will be logged: id controls the id property; mutationTypeID controls the id property of the mutation’s mutation type (this will be the same for all mutations logged by a given MutationType, it can be useful if you combine datasets from more than one mutation type later); chromosomeID controls the id property of the mutation’s associated chromosome; position controls the position property; nucleotideValue controls the nucleotideValue property; originTick controls the originTick property; subpopID controls the subpopID property; and tag controls the tag property.  Data columns will be added in the order of these parameters; the id column will be first, if requested, for example.

+

Last come parameters that control the logging of trait-associated data for the new mutations.  The trait parameter controls which traits will be logged.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  For each specified trait, the effect parameter controls logging of the effect size, the dominance parameter controls the dominance, and the hemizygousDominance parameter controls the hemizygous dominance.  Data columns for this trait-associated data will be grouped by trait; for example, if two traits named height and weight are specified, and the effect=T and dominance=T flags are specified, then columns heightEffect, heightDominance, weightEffect, and weightDominance will be added, in that order.

+

Note that logging occurs after all mutation() callbacks have been called, at the point when the new mutation is actually added to the simulation.  If addition of a new mutation is prevented, by a mutation() callback or by the current stacking policy, that mutation will not be logged.  The information logged will be the mutation’s properties at the moment that it is added; a tag value set by a mutation() callback will therefore be captured in the logged data, for example.  Changes to mutations made after that point will not be preserved in the log; the log is a snapshot of the moment of each mutation’s addition to the simulation.

– (void)setDefaultDominanceForTrait(Niso<Trait> trait, float dominance)

Set the default dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of defaultDominance is used for each corresponding trait).

As for initializeMutationType(), a dominance value of NAN for a given trait configures the mutation type to use “independent dominance” for that trait in new mutations of that type; see the class Trait documentation for discussion of independent dominance.  A mutation type may be configured to use independent dominance for some traits and not for others; a mixed configuration is allowed.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index bd638fdf..b49dec1d 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -7015,6 +7015,178 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 class documentation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 \'96\'a0(fo)loggedData(string$\'a0kind, [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effect\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns mutation data produced by the mutation type\'92s logging facility, as configured by +\f3\fs18 logMutationData() +\f4\fs20 . The data returned can be in the form of means across all logged mutations (for +\f3\fs18 kind="mean" +\f4\fs20 ), standard deviations across all logged mutations (for +\f3\fs18 kind="sd" +\f4\fs20 ), or separate values for each mutation (for +\f3\fs18 kind="values" +\f4\fs20 ). If logging only of means was enabled (with the +\f3\fs18 meanOnly=T +\f4\fs20 option to +\f3\fs18 logMutationData() +\f4\fs20 ), only +\f3\fs18 kind="mean" +\f4\fs20 is allowed, since separate values for each mutation are then not logged.\ +The remaining flags control which columns of data should be returned; see +\f3\fs18 logMutationData() +\f4\fs20 for a summary of the mutation properties they refer to. If only one data column is specified by the flags, a vector of values (or a mean or standard deviation) will be returned for that one specified data column. If more than one flag is set to +\f3\fs18 T +\f4\fs20 , a +\f3\fs18 DataFrame +\f4\fs20 object will be returned with named columns of values (or means, or standard deviations) for each specified data column. The +\f3\fs18 trait +\f4\fs20 property specifies which traits values should be returned for, with respect to the +\f3\fs18 effect +\f4\fs20 , +\f3\fs18 dominance +\f4\fs20 , and +\f3\fs18 hemizygousDominance +\f4\fs20 flags; see +\f3\fs18 logMutationData() +\f4\fs20 for further description. If +\f1\i all +\f4\i0 of these flags are +\f3\fs18 F +\f4\fs20 (the default), that is taken to mean that +\f1\i all +\f4\i0 logged data should be returned; in that case, a vector will be returned if only one column of data was logged, otherwise a +\f3\fs18 DataFrame +\f4\fs20 object will be returned, the same as when flags are specified explicitly. Flags set to +\f3\fs18 T +\f4\fs20 for data columns that were not actually logged will simply be ignored; similarly, traits specified by +\f3\fs18 trait +\f4\fs20 that were not actually logged will simply be ignored. See the Eidos manual for the +\f3\fs18 DataFrame +\f4\fs20 class documentation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(void)logMutationData(logical$\'a0enable, [logical$\'a0autogeneratedOnly\'a0=\'a0T], [logical$\'a0meanOnly\'a0=\'a0F], [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effect\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Starts or ends logging of data about new mutations belonging to the target mutation type. If +\f3\fs18 autogeneratedOnly +\f4\fs20 is +\f3\fs18 T +\f4\fs20 (the default), only new mutations generated automatically by SLiM will be logged (including mutations that are substituted in for an auto-generated mutation using a +\f3\fs18 mutation() +\f4\fs20 callback; that is still considered part of the auto-generation process). If +\f3\fs18 autogeneratedOnly +\f4\fs20 is +\f3\fs18 F +\f4\fs20 , mutations generated in script, such as with +\f3\fs18 addNewMutation() +\f4\fs20 , +\f3\fs18 addNewDrawnMutation() +\f4\fs20 , and reading from files such as VCF, MS, or +\f3\fs18 .trees +\f4\fs20 , will also be logged. The logged information can be obtained later with the +\f3\fs18 loggedData() +\f4\fs20 method. Once logging has been started with +\f3\fs18 enable=T +\f4\fs20 it cannot be modified, only stopped with +\f3\fs18 enable=F +\f4\fs20 ; and if logging is subsequently resumed with +\f3\fs18 enable=T +\f4\fs20 , any previously logged data will be discarded. (This can be useful if you wish to limit the size of the in-memory data while continuing to log new data: periodically write the accumulated data to a file and then disable and re-enable logging to discard the old data.)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 If +\f3\fs18 meanOnly +\f4\fs20 is +\f3\fs18 F +\f4\fs20 (the default), values for each new mutation will be kept separately. Beware: the memory usage entailed by this option can be extremely large! Alternatively, if +\f3\fs18 meanOnly +\f4\fs20 is +\f3\fs18 T +\f4\fs20 , only a running sum, used to compute a mean, will be kept for each type of data; the memory usage for this option will be small and constant, but of course a mean is more useful for some columns of data than others. If per-mutation data is desired for any one column, use +\f3\fs18 meanOnly=F +\f4\fs20 ; this option cannot be controlled independently for the various columns of data being logged.\ +Next are parameters that control which mutation properties will be logged: +\f3\fs18 id +\f4\fs20 controls the +\f3\fs18 id +\f4\fs20 property; +\f3\fs18 mutationTypeID +\f4\fs20 controls the +\f3\fs18 id +\f4\fs20 property of the mutation\'92s mutation type (this will be the same for all mutations logged by a given +\f3\fs18 MutationType +\f4\fs20 , it can be useful if you combine datasets from more than one mutation type later); +\f3\fs18 chromosomeID +\f4\fs20 controls the +\f3\fs18 id +\f4\fs20 property of the mutation\'92s associated chromosome; +\f3\fs18 position +\f4\fs20 controls the +\f3\fs18 position +\f4\fs20 property; +\f3\fs18 nucleotideValue +\f4\fs20 controls the +\f3\fs18 nucleotideValue +\f4\fs20 property; +\f3\fs18 originTick +\f4\fs20 controls the +\f3\fs18 originTick +\f4\fs20 property; +\f3\fs18 subpopID +\f4\fs20 controls the +\f3\fs18 subpopID +\f4\fs20 property; and +\f3\fs18 tag +\f4\fs20 controls the +\f3\fs18 tag +\f4\fs20 property. Data columns will be added in the order of these parameters; the +\f3\fs18 id +\f4\fs20 column will be first, if requested, for example.\ +Last come parameters that control the logging of trait-associated data for the new mutations. The +\f3\fs18 trait +\f4\fs20 parameter controls which traits will be logged. The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species. For each specified trait, the +\f3\fs18 effect +\f4\fs20 parameter controls logging of the effect size, the +\f3\fs18 dominance +\f4\fs20 parameter controls the dominance, and the +\f3\fs18 hemizygousDominance +\f4\fs20 parameter controls the hemizygous dominance. Data columns for this trait-associated data will be grouped by trait; for example, if two traits named +\f3\fs18 height +\f4\fs20 and +\f3\fs18 weight +\f4\fs20 are specified, and the +\f3\fs18 effect=T +\f4\fs20 and +\f3\fs18 dominance=T +\f4\fs20 flags are specified, then columns +\f3\fs18 heightEffect +\f4\fs20 , +\f3\fs18 heightDominance +\f4\fs20 , +\f3\fs18 weightEffect +\f4\fs20 , and +\f3\fs18 weightDominance +\f4\fs20 will be added, in that order.\ +Note that logging occurs after all +\f3\fs18 mutation() +\f4\fs20 callbacks have been called, at the point when the new mutation is actually added to the simulation. If addition of a new mutation is prevented, by a +\f3\fs18 mutation() +\f4\fs20 callback or by the current stacking policy, that mutation will not be logged. The information logged will be the mutation\'92s properties at the moment that it is added; a +\f3\fs18 tag +\f4\fs20 value set by a +\f3\fs18 mutation() +\f4\fs20 callback will therefore be captured in the logged data, for example. Changes to mutations made after that point will not be preserved in the log; the log is a snapshot of the moment of each mutation\'92s addition to the simulation.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 \'96\'a0(void)setDefaultDominanceForTrait(Niso\'a0trait, float\'a0dominance)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 diff --git a/VERSIONS b/VERSIONS index e67bbe98..dfa05f0f 100644 --- a/VERSIONS +++ b/VERSIONS @@ -136,6 +136,10 @@ multitrait branch: add zygosityOfMutations([No mutations = NULL], [integer$ hemizygousValue = 1], [integer$ haploidValue = 1]) for assessing zygosity (see also mutationsFromHaplosomes()) add SLiMgui doc for the various dynamic properties added for traits add a Species demandPhenotype() method, and change the Individual method demandPhenotype() to demandPhenotypeForIndividuals(); the Species method is generally preferred for speed + add data logging capabilities to MutationType for easier assessment of requested vs. realized DFEs, etc. + - (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], ) + - (fo)loggedData(string$ kind, ) + policy change: mean(integer(0)) or mean(float(0)) now returns NAN rather than NULL, to match R; I just noticed this, and NAN does seem better version 5.1 (Eidos version 4.1): diff --git a/core/haplosome.cpp b/core/haplosome.cpp index b4a3e3b4..3ffeacdf 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2983,7 +2983,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // add to the registry, return value, haplosome, etc. if (new_mut->state_ != MutationState::kInRegistry) - pop.MutationRegistryAdd(new_mut); + pop.MutationRegistryAdd(new_mut, /* p_autogenerated */ false); retval->push_object_element_RR(new_mut); mutations_to_add.emplace_back(new_mut_index); } @@ -3476,7 +3476,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry - pop.MutationRegistryAdd(new_mut); + pop.MutationRegistryAdd(new_mut, /* p_autogenerated */ false); mutation_indices.emplace_back(new_mut_index); } @@ -4124,7 +4124,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt } // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry - pop.MutationRegistryAdd(new_mut); + pop.MutationRegistryAdd(new_mut, /* p_autogenerated */ false); alt_allele_mut_indices.emplace_back(new_mut_index); mutation_indices.emplace_back(new_mut_index); } diff --git a/core/individual.cpp b/core/individual.cpp index e7a42f58..5a7a1609 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -5498,7 +5498,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal } // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry - pop.MutationRegistryAdd(new_mut); + pop.MutationRegistryAdd(new_mut, /* p_autogenerated */ false); alt_allele_mut_indices.emplace_back(new_mut_index); mutation_indices.emplace_back(new_mut_index); } diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 38a0b729..278f874f 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -27,6 +27,7 @@ #include "species.h" #include "community.h" #include "mutation_block.h" +#include "eidos_class_DataFrame.h" #include #include @@ -129,6 +130,136 @@ MutationType::~MutationType(void) keeping_muttype_registry_ = false; } #endif + + FreeLoggingInfo(); +} + +void MutationType::FreeLoggingInfo(void) +{ + if (logged_id_) { free(logged_id_); logged_id_ = nullptr; } + if (logged_muttype_id_) { free(logged_muttype_id_); logged_muttype_id_ = nullptr; } + if (logged_chromosome_id_) { free(logged_chromosome_id_); logged_chromosome_id_ = nullptr; } + if (logged_position_) { free(logged_position_); logged_position_ = nullptr; } + if (logged_nucleotide_) { free(logged_nucleotide_); logged_nucleotide_ = nullptr; } + if (logged_origin_tick_) { free(logged_origin_tick_); logged_origin_tick_ = nullptr; } + if (logged_subpop_id_) { free(logged_subpop_id_); logged_subpop_id_ = nullptr; } + if (logged_tag_) { free(logged_tag_); logged_tag_ = nullptr; } + + logged_trait_indices.resize(0); + + for (TraitEffectLog &trait_effect_log : logged_traits_) + { + if (trait_effect_log.logged_effect_) { free(trait_effect_log.logged_effect_); trait_effect_log.logged_effect_ = nullptr; } + if (trait_effect_log.logged_dominance_) { free(trait_effect_log.logged_dominance_); trait_effect_log.logged_dominance_ = nullptr; } + if (trait_effect_log.logged_hemizygous_dominance_) { free(trait_effect_log.logged_hemizygous_dominance_); trait_effect_log.logged_hemizygous_dominance_ = nullptr; } + } + + logged_traits_.resize(0); + + mutation_logging_on_ = false; +} + +void MutationType::_LogMutationInfo(Mutation *p_mut) +{ +#if DEBUG + if (!mutation_logging_on_) + EIDOS_TERMINATION << "ERROR (MutationType::_LogMutationInfo): (internal error) called when mutation logging is not enabled." << EidosTerminate(); +#endif + + // check for an illegal property access + if (log_tag_ && (p_mut->tag_value_ == SLIM_TAG_UNSET_VALUE)) + EIDOS_TERMINATION << "ERROR (MutationType::_LogMutationInfo): property tag accessed on mutation before being set (logging of mutation tag values is enabled, but the tag value of the mutation being logged has not been set)." << EidosTerminate(); + + // increase capacity if needed + if (!log_meanOnly_ && (log_size_ == log_capacity_)) + { + log_capacity_ <<= 1; + + if (logged_id_) logged_id_ = (slim_mutationid_t *)realloc(logged_id_, log_capacity_ * sizeof(slim_mutationid_t)); + if (logged_muttype_id_) logged_muttype_id_ = (slim_objectid_t *)realloc(logged_muttype_id_, log_capacity_ * sizeof(slim_objectid_t)); + if (logged_chromosome_id_) logged_chromosome_id_ = (int64_t *)realloc(logged_chromosome_id_, log_capacity_ * sizeof(int64_t)); + if (logged_position_) logged_position_ = (slim_position_t *)realloc(logged_position_, log_capacity_ * sizeof(slim_position_t)); + if (logged_nucleotide_) logged_nucleotide_ = (int8_t *)realloc(logged_nucleotide_, log_capacity_ * sizeof(int8_t)); + if (logged_origin_tick_) logged_origin_tick_ = (slim_tick_t *)realloc(logged_origin_tick_, log_capacity_ * sizeof(slim_tick_t)); + if (logged_subpop_id_) logged_subpop_id_ = (slim_objectid_t *)realloc(logged_subpop_id_, log_capacity_ * sizeof(slim_objectid_t)); + if (logged_tag_) logged_tag_ = (slim_usertag_t *)realloc(logged_tag_, log_capacity_ * sizeof(slim_usertag_t)); + + for (slim_trait_index_t trait_index : logged_trait_indices) + { + TraitEffectLog &trait_log = logged_traits_[trait_index]; + + if (log_effect_) trait_log.logged_effect_ = (slim_effect_t *)realloc(trait_log.logged_effect_, log_capacity_ * sizeof(slim_effect_t)); + if (log_dominance_) trait_log.logged_dominance_ = (slim_effect_t *)realloc(trait_log.logged_dominance_, log_capacity_ * sizeof(slim_effect_t)); + if (log_hemizygousDominance_) trait_log.logged_hemizygous_dominance_ = (slim_effect_t *)realloc(trait_log.logged_hemizygous_dominance_, log_capacity_ * sizeof(slim_effect_t)); + } + } + + // update logged data -- running sums and, if !log_meanOnly_, also our logging buffers + if (log_id_) { + slim_mutationid_t id = p_mut->mutation_id_; + running_id_ += p_mut->mutation_id_; + if (!log_meanOnly_) logged_id_[log_size_] = id; + } + if (log_mutationTypeID_) { + slim_objectid_t muttypeID = p_mut->mutation_type_ptr_->mutation_type_id_; + running_muttype_id_ += muttypeID; + if (!log_meanOnly_) logged_muttype_id_[log_size_] = muttypeID; + } + if (log_chromosomeID_) { + int64_t chromosomeID = species_.Chromosomes()[p_mut->chromosome_index_]->ID(); + running_chromosome_id_ += chromosomeID; + if (!log_meanOnly_) logged_chromosome_id_[log_size_] = chromosomeID; + } + if (log_position_) { + slim_position_t position = p_mut->position_; + running_position_ += position; + if (!log_meanOnly_) logged_position_[log_size_] = position; + } + if (log_nucleotideValue_) { + int8_t nucleotide = p_mut->nucleotide_; + running_nucleotide_ += nucleotide; + if (!log_meanOnly_) logged_nucleotide_[log_size_] = nucleotide; + } + if (log_originTick_) { + slim_tick_t originTick = p_mut->origin_tick_; + running_origin_tick_ += originTick; + if (!log_meanOnly_) logged_origin_tick_[log_size_] = originTick; + } + if (log_subpopID_) { + slim_objectid_t subpopID = p_mut->subpop_index_; + running_subpop_id_ += subpopID; + if (!log_meanOnly_) logged_subpop_id_[log_size_] = subpopID; + } + if (log_tag_) { + slim_usertag_t tag = p_mut->tag_value_; + running_tag_ += tag; + if (!log_meanOnly_) logged_tag_[log_size_] = tag; + } + + for (slim_trait_index_t trait_index : logged_trait_indices) + { + TraitEffectLog &trait_log = logged_traits_[trait_index]; + MutationTraitInfo &mut_trait_info = species_.SpeciesMutationBlock()->TraitInfoForMutation(p_mut)[trait_index]; + + if (log_effect_) { + slim_effect_t effect = mut_trait_info.effect_size_; + trait_log.running_effect_ += effect; + if (!log_meanOnly_) trait_log.logged_effect_[log_size_] = effect; + } + if (log_dominance_) { + slim_effect_t dominance = p_mut->RealizedDominanceForTrait(species_.Traits()[trait_index]); + trait_log.running_dominance_ += dominance; + if (!log_meanOnly_) trait_log.logged_dominance_[log_size_] = dominance; + } + if (log_hemizygousDominance_) { + slim_effect_t hemizygousDominance = mut_trait_info.hemizygous_dominance_coeff_; + trait_log.running_hemizygous_dominance_ += hemizygousDominance; + if (!log_meanOnly_) trait_log.logged_hemizygous_dominance_[log_size_] = hemizygousDominance; + } + } + + // finally, record that we have added one entry + log_size_++; } void MutationType::ParseDESParameters(std::string &p_DES_type_string, const EidosValue_SP *const p_arguments, int p_argument_count, DESType *p_DES_type, std::vector *p_DES_parameters, std::vector *p_DES_strings) @@ -701,6 +832,8 @@ EidosValue_SP MutationType::ExecuteInstanceMethod(EidosGlobalStringID p_method_i case gID_effectDistributionTypeForTrait: return ExecuteMethod_effectDistributionTypeForTrait(p_method_id, p_arguments, p_interpreter); case gID_effectDistributionParamsForTrait: return ExecuteMethod_effectDistributionParamsForTrait(p_method_id, p_arguments, p_interpreter); case gID_drawEffectForTrait: return ExecuteMethod_drawEffectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_loggedData: return ExecuteMethod_loggedData(p_method_id, p_arguments, p_interpreter); + case gID_logMutationData: return ExecuteMethod_logMutationData(p_method_id, p_arguments, p_interpreter); case gID_setDefaultDominanceForTrait: return ExecuteMethod_setDefaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_setDefaultHemizygousDominanceForTrait: return ExecuteMethod_setDefaultHemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_setEffectDistributionForTrait: return ExecuteMethod_setEffectDistributionForTrait(p_method_id, p_arguments, p_interpreter); @@ -894,6 +1027,403 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID } } +// ********************* - (fo)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], +// [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], +// [Niso trait = NULL], [l$ effect = F], [l$ dominance = F], [l$ hemizygousDominance = F]) +// +EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *kind_value = p_arguments[0].get(); + EidosValue *id_value = p_arguments[1].get(); + EidosValue *mutationTypeID_value = p_arguments[2].get(); + EidosValue *chromosomeID_value = p_arguments[3].get(); + EidosValue *position_value = p_arguments[4].get(); + EidosValue *nucleotideValue_value = p_arguments[5].get(); + EidosValue *originTick_value = p_arguments[6].get(); + EidosValue *subpopID_value = p_arguments[7].get(); + EidosValue *tag_value = p_arguments[8].get(); + EidosValue *trait_value = p_arguments[9].get(); + EidosValue *effect_value = p_arguments[10].get(); + EidosValue *dominance_value = p_arguments[11].get(); + EidosValue *hemizygousDominance_value = p_arguments[12].get(); + + if (!mutation_logging_on_) + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_loggedData): mutation logging is not currently enabled, so logged data cannot be fetched." << EidosTerminate(nullptr); + + // kind + typedef enum class _KindEnum { + kMean = 1, + kSD, + kValues + } KindEnum; + + EidosValue_String *kind_value_string = (EidosValue_String *)kind_value; + const std::string &kind_str = kind_value_string->StringRefAtIndex_NOCAST(0, nullptr); + KindEnum kind; + + if (kind_str == "mean") kind = KindEnum::kMean; + else if (kind_str == "sd") kind = KindEnum::kSD; + else if (kind_str == "values") kind = KindEnum::kValues; + else + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_loggedData): loggedData() requires that kind be 'mean', 'sd', or 'values'." << EidosTerminate(nullptr); + + if (log_meanOnly_ && (kind != KindEnum::kMean)) + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_loggedData): loggedData() can only return means (kind='mean'), since meanOnly=T was set in logMutationData()." << EidosTerminate(nullptr); + + // trait + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "loggedData"); + + // narrow down to just the traits that were logged, silently skipping those that weren't (so the user can just supply NULL to get all logged traits) + std::vector get_trait_indices; + + for (slim_trait_index_t trait_index : trait_indices) + { + if (std::find(logged_trait_indices.begin(), logged_trait_indices.end(), trait_index) == logged_trait_indices.end()) + continue; + + get_trait_indices.push_back(trait_index); + } + + int trait_count = (int)get_trait_indices.size(); + + // all other logical flags + bool get_id = id_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_mutationTypeID = mutationTypeID_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_chromosomeID = chromosomeID_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_position = position_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_nucleotideValue = nucleotideValue_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_originTick = originTick_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_subpopID = subpopID_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_tag = tag_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_effect = effect_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_dominance = dominance_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_hemizygousDominance = hemizygousDominance_value->LogicalAtIndex_NOCAST(0, nullptr); + + // if all flags are false, that is a special semantic that means all flags that were logged are true (so the + // user can either use the default signature and get everything that was logged, OR pass flag=T to get the + // ones they want without having to pass F for others) + if (!get_id && !get_mutationTypeID && !get_chromosomeID && !get_position && !get_nucleotideValue && + !get_originTick && !get_subpopID && !get_tag && !get_effect && !get_dominance && !get_hemizygousDominance) + { + get_id = log_id_; + get_mutationTypeID = log_mutationTypeID_; + get_chromosomeID = log_chromosomeID_; + get_position = log_position_; + get_nucleotideValue = log_nucleotideValue_; + get_originTick = log_originTick_; + get_subpopID = log_subpopID_; + get_tag = log_tag_; + get_effect = log_effect_; + get_dominance = log_dominance_; + get_hemizygousDominance = log_hemizygousDominance_; + } + + // then narrow down to the flags that were actually logged, silently skipping those that weren't + if (get_id && !log_id_) get_id = false; + if (get_mutationTypeID && !log_mutationTypeID_) get_mutationTypeID = false; + if (get_chromosomeID && !log_chromosomeID_) get_chromosomeID = false; + if (get_position && !log_position_) get_position = false; + if (get_nucleotideValue && !log_nucleotideValue_) get_nucleotideValue = false; + if (get_originTick && !log_originTick_) get_originTick = false; + if (get_subpopID && !log_subpopID_) get_subpopID = false; + if (get_tag && !log_tag_) get_tag = false; + if (get_effect && !log_effect_) get_effect = false; + if (get_dominance && !log_dominance_) get_dominance = false; + if (get_hemizygousDominance && !log_hemizygousDominance_) get_hemizygousDominance = false; + + // figure out how many separate items we're actually going to return + int requested_count = (int)get_id + (int)get_mutationTypeID + (int)get_chromosomeID + (int)get_position + + (int)get_nucleotideValue + (int)get_originTick + (int)get_subpopID + (int)get_tag + + ((int)get_effect) * trait_count + ((int)get_dominance) * trait_count + ((int)get_hemizygousDominance) * trait_count; + + EidosDataFrame *dataframe = nullptr; + EidosValue_SP result_SP; + + if (requested_count > 1) + { + // we need to construct a DataFrame object; set it up + dataframe = new EidosDataFrame(); + result_SP = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(dataframe, gEidosDataFrame_Class)); + + // dataframe is now retained by result_SP, so we can release it + dataframe->Release(); + } + + EidosValue *column; + + if (get_id) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_id_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Int())->resize_no_initialize(log_size_); + int64_t *column_data = column->IntData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = logged_id_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys("id", EidosValue_SP(column)); + } + + if (get_mutationTypeID) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_muttype_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_muttype_id_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Int())->resize_no_initialize(log_size_); + int64_t *column_data = column->IntData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = logged_muttype_id_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys("mutationTypeID", EidosValue_SP(column)); + } + + if (get_chromosomeID) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_chromosome_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_chromosome_id_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Int())->resize_no_initialize(log_size_); + int64_t *column_data = column->IntData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = logged_chromosome_id_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys("chromosomeID", EidosValue_SP(column)); + } + + if (get_position) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_position_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_position_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Int())->resize_no_initialize(log_size_); + int64_t *column_data = column->IntData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = logged_position_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys("position", EidosValue_SP(column)); + } + + if (get_nucleotideValue) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_nucleotide_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_nucleotide_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Int())->resize_no_initialize(log_size_); + int64_t *column_data = column->IntData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = logged_nucleotide_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys("nucleotideValue", EidosValue_SP(column)); + } + + if (get_originTick) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_origin_tick_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_origin_tick_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Int())->resize_no_initialize(log_size_); + int64_t *column_data = column->IntData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = logged_origin_tick_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys("originTick", EidosValue_SP(column)); + } + + if (get_subpopID) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_subpop_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_subpop_id_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Int())->resize_no_initialize(log_size_); + int64_t *column_data = column->IntData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = logged_subpop_id_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys("subpopID", EidosValue_SP(column)); + } + + if (get_tag) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_tag_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_tag_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Int())->resize_no_initialize(log_size_); + int64_t *column_data = column->IntData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = logged_tag_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys("tag", EidosValue_SP(column)); + } + + for (slim_trait_index_t trait_index : get_trait_indices) + { + TraitEffectLog &trait_log = logged_traits_[trait_index]; + const std::string &trait_name = species_.Traits()[trait_index]->Name(); + + if (get_effect) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(trait_log.running_effect_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_effect_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Float())->resize_no_initialize(log_size_); + double *column_data = column->FloatData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = (double)trait_log.logged_effect_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys(trait_name + "Effect", EidosValue_SP(column)); + } + + if (get_dominance) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(trait_log.running_dominance_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_dominance_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Float())->resize_no_initialize(log_size_); + double *column_data = column->FloatData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = (double)trait_log.logged_dominance_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys(trait_name + "Dominance", EidosValue_SP(column)); + } + + if (get_hemizygousDominance) + { + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(trait_log.running_hemizygous_dominance_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_hemizygous_dominance_, log_size_)); + else /* kind == KindEnum::kValues */ { + column = (new EidosValue_Float())->resize_no_initialize(log_size_); + double *column_data = column->FloatData_Mutable(); + for (size_t log_index = 0; log_index < log_size_; log_index++) + column_data[log_index] = (double)trait_log.logged_hemizygous_dominance_[log_index]; + } + if (requested_count == 1) return EidosValue_SP(column); + else dataframe->SetKeyValue_StringKeys(trait_name + "HemizygousDominance", EidosValue_SP(column)); + } + } + + dataframe->ContentsChanged("MutationType::ExecuteMethod_loggedData()"); + + return result_SP; + +} + +// ********************* - (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], [logical$ id = F], [logical$ mutationTypeID = F], +// [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], +// [logical$ subpopID = F], [logical$ tag = F], [Niso trait = NULL], [logical$ effect = F], [logical$ dominance = F], +// [logical$ hemizygousDominance = F]) +// +EidosValue_SP MutationType::ExecuteMethod_logMutationData(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *enable_value = p_arguments[0].get(); + EidosValue *autogeneratedOnly_value = p_arguments[1].get(); + EidosValue *meanOnly_value = p_arguments[2].get(); + EidosValue *id_value = p_arguments[3].get(); + EidosValue *mutationTypeID_value = p_arguments[4].get(); + EidosValue *chromosomeID_value = p_arguments[5].get(); + EidosValue *position_value = p_arguments[6].get(); + EidosValue *nucleotideValue_value = p_arguments[7].get(); + EidosValue *originTick_value = p_arguments[8].get(); + EidosValue *subpopID_value = p_arguments[9].get(); + EidosValue *tag_value = p_arguments[10].get(); + EidosValue *trait_value = p_arguments[11].get(); + EidosValue *effect_value = p_arguments[12].get(); + EidosValue *dominance_value = p_arguments[13].get(); + EidosValue *hemizygousDominance_value = p_arguments[14].get(); + + bool f_enable = enable_value->LogicalAtIndex_NOCAST(0, nullptr); + + if (!f_enable) + { + if (!mutation_logging_on_) + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_logMutationData): mutation logging is not currently enabled, so disabling logging is not permitted." << EidosTerminate(nullptr); + + mutation_logging_on_ = false; + + // note we don't deallocate any existing logs; we want the user to be able to continue accessing logs after stopping. + + return gStaticEidosValueVOID; + } + + // from here on enable == T, and we are configuring the new logging we are to begin + if (mutation_logging_on_) + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_logMutationData): mutation logging is already enabled; modification of logging settings is not permitted (but you can disable logging with enable=F and then restart logging with new settings)." << EidosTerminate(nullptr); + + FreeLoggingInfo(); // free any existing logging info + + mutation_logging_on_ = true; // then turn logging on and set up new info + + // trait + species_.GetTraitIndicesFromEidosValue(logged_trait_indices, trait_value, "logMutationData"); + + // all other logical flags + log_autogeneratedOnly_ = autogeneratedOnly_value->LogicalAtIndex_NOCAST(0, nullptr); + log_meanOnly_ = meanOnly_value->LogicalAtIndex_NOCAST(0, nullptr); + + log_id_ = id_value->LogicalAtIndex_NOCAST(0, nullptr); + log_mutationTypeID_ = mutationTypeID_value->LogicalAtIndex_NOCAST(0, nullptr); + log_chromosomeID_ = chromosomeID_value->LogicalAtIndex_NOCAST(0, nullptr); + log_position_ = position_value->LogicalAtIndex_NOCAST(0, nullptr); + log_nucleotideValue_ = nucleotideValue_value->LogicalAtIndex_NOCAST(0, nullptr); + log_originTick_ = originTick_value->LogicalAtIndex_NOCAST(0, nullptr); + log_subpopID_ = subpopID_value->LogicalAtIndex_NOCAST(0, nullptr); + log_tag_ = tag_value->LogicalAtIndex_NOCAST(0, nullptr); + log_effect_ = effect_value->LogicalAtIndex_NOCAST(0, nullptr); + log_dominance_ = dominance_value->LogicalAtIndex_NOCAST(0, nullptr); + log_hemizygousDominance_ = hemizygousDominance_value->LogicalAtIndex_NOCAST(0, nullptr); + + if (log_nucleotideValue_ && !species_.IsNucleotideBased()) + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_logMutationData): logging of nucleotide data is only supported in nucleotide-based models." << EidosTerminate(nullptr); + if ((logged_trait_indices.size() == 0) && (log_effect_ || log_dominance_ || log_hemizygousDominance_)) + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_logMutationData): logging of effect, dominance, and hemizygous dominance is enabled, but no traits to log were specified." << EidosTerminate(nullptr); + + // allocate log pointers and running sums + log_size_ = 0; + log_capacity_ = 0; + + if (!log_meanOnly_) + { + log_capacity_ = 1024; // initial capacity + + if (log_id_) { running_id_ = 0.0; logged_id_ = (slim_mutationid_t *)malloc(log_capacity_ * sizeof(slim_mutationid_t)); } + if (log_mutationTypeID_) { running_muttype_id_ = 0.0; logged_muttype_id_ = (slim_objectid_t *)malloc(log_capacity_ * sizeof(slim_objectid_t)); } + if (log_chromosomeID_) { running_chromosome_id_ = 0.0; logged_chromosome_id_ = (int64_t *)malloc(log_capacity_ * sizeof(int64_t)); } + if (log_position_) { running_position_ = 0.0; logged_position_ = (slim_position_t *)malloc(log_capacity_ * sizeof(slim_position_t)); } + if (log_nucleotideValue_) { running_nucleotide_ = 0.0; logged_nucleotide_ = (int8_t *)malloc(log_capacity_ * sizeof(int8_t)); } + if (log_originTick_) { running_origin_tick_ = 0.0; logged_origin_tick_ = (slim_tick_t *)malloc(log_capacity_ * sizeof(slim_tick_t)); } + if (log_subpopID_) { running_subpop_id_ = 0.0; logged_subpop_id_ = (slim_objectid_t *)malloc(log_capacity_ * sizeof(slim_objectid_t)); } + if (log_tag_) { running_tag_ = 0.0; logged_tag_ = (slim_usertag_t *)malloc(log_capacity_ * sizeof(slim_usertag_t)); } + + if (logged_trait_indices.size()) + { + logged_traits_.resize(species_.TraitCount()); // note we have an entry for each trait in the species, but it may be that not all entries are used; that depends on logged_trait_indices + + for (slim_trait_index_t trait_index : logged_trait_indices) + { + TraitEffectLog &trait_effect_log = logged_traits_[trait_index]; + + if (log_effect_) { trait_effect_log.running_effect_ = 0.0; trait_effect_log.logged_effect_ = (slim_effect_t *)malloc(log_capacity_ * sizeof(slim_effect_t)); } + if (log_dominance_) { trait_effect_log.running_dominance_ = 0.0; trait_effect_log.logged_dominance_ = (slim_effect_t *)malloc(log_capacity_ * sizeof(slim_effect_t)); } + if (log_hemizygousDominance_) { trait_effect_log.running_hemizygous_dominance_ = 0.0; trait_effect_log.logged_hemizygous_dominance_ = (slim_effect_t *)malloc(log_capacity_ * sizeof(slim_effect_t)); } + } + } + } + + return gStaticEidosValueVOID; +} + // ********************* - (void)setDefaultDominanceForTrait(Niso trait, float dominance) // EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -931,7 +1461,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba } } else - EIDOS_TERMINATION << "ERROR (ExecuteMethod_setDefaultDominanceForTrait): setDefaultDominanceForTrait() requires parameter dominance to be of length 1, or equal in length to the number of specified traits." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_setDefaultDominanceForTrait): setDefaultDominanceForTrait() requires parameter dominance to be of length 1, or equal in length to the number of specified traits." << EidosTerminate(nullptr); // BCH 7/2/2025: Changing the default dominance coefficient no longer means that the cached fitness // effects of all mutations using this type become invalid; it is now just the *default* coefficient, @@ -981,7 +1511,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( } } else - EIDOS_TERMINATION << "ERROR (ExecuteMethod_setDefaultHemizygousDominanceForTrait): setDefaultHemizygousDominanceForTrait() requires parameter dominance to be of length 1, or equal in length to the number of specified traits." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait): setDefaultHemizygousDominanceForTrait() requires parameter dominance to be of length 1, or equal in length to the number of specified traits." << EidosTerminate(nullptr); // BCH 7/2/2025: Changing the default dominance coefficient no longer means that the cached fitness // effects of all mutations using this type become invalid; it is now just the *default* coefficient, @@ -989,7 +1519,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( // still want to let the species know that a mutation type has changed, though. species_.AutogenerationConfigurationChanged(); - SelfConsistencyCheck(" in setDefaultDominanceForTrait()"); + SelfConsistencyCheck(" in setDefaultHemizygousDominanceForTrait()"); return gStaticEidosValueVOID; } @@ -1083,6 +1613,36 @@ const std::vector *MutationType_Class::Methods(void) c methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_loggedData, kEidosValueMaskFloat | kEidosValueMaskObject, gEidosDataFrame_Class)) + ->AddString_S("kind") + ->AddLogical_OS("id", gStaticEidosValue_LogicalF) + ->AddLogical_OS("mutationTypeID", gStaticEidosValue_LogicalF) + ->AddLogical_OS("chromosomeID", gStaticEidosValue_LogicalF) + ->AddLogical_OS("position", gStaticEidosValue_LogicalF) + ->AddLogical_OS("nucleotideValue", gStaticEidosValue_LogicalF) + ->AddLogical_OS("originTick", gStaticEidosValue_LogicalF) + ->AddLogical_OS("subpopID", gStaticEidosValue_LogicalF) + ->AddLogical_OS("tag", gStaticEidosValue_LogicalF) + ->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL) + ->AddLogical_OS("effect", gStaticEidosValue_LogicalF) + ->AddLogical_OS("dominance", gStaticEidosValue_LogicalF) + ->AddLogical_OS("hemizygousDominance", gStaticEidosValue_LogicalF)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_logMutationData, kEidosValueMaskVOID)) + ->AddLogical_S("enable") + ->AddLogical_OS("autogeneratedOnly", gStaticEidosValue_LogicalT) + ->AddLogical_OS("meanOnly", gStaticEidosValue_LogicalF) + ->AddLogical_OS("id", gStaticEidosValue_LogicalF) + ->AddLogical_OS("mutationTypeID", gStaticEidosValue_LogicalF) + ->AddLogical_OS("chromosomeID", gStaticEidosValue_LogicalF) + ->AddLogical_OS("position", gStaticEidosValue_LogicalF) + ->AddLogical_OS("nucleotideValue", gStaticEidosValue_LogicalF) + ->AddLogical_OS("originTick", gStaticEidosValue_LogicalF) + ->AddLogical_OS("subpopID", gStaticEidosValue_LogicalF) + ->AddLogical_OS("tag", gStaticEidosValue_LogicalF) + ->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL) + ->AddLogical_OS("effect", gStaticEidosValue_LogicalF) + ->AddLogical_OS("dominance", gStaticEidosValue_LogicalF) + ->AddLogical_OS("hemizygousDominance", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectDistributionForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); diff --git a/core/mutation_type.h b/core/mutation_type.h index ac7296ba..f78572d7 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -74,6 +74,13 @@ typedef struct _EffectDistributionInfo { mutable EidosScript *cached_DES_script_ = nullptr; // used by DES type 's' to hold a cached script for the DES } EffectDistributionInfo; +// This struct holds per-trait information about mutations, for our mutation data logging facility +typedef struct _TraitEffectLog { + double running_effect_, running_dominance_, running_hemizygous_dominance_; + slim_effect_t *logged_effect_ = nullptr; + slim_effect_t *logged_dominance_ = nullptr; + slim_effect_t *logged_hemizygous_dominance_ = nullptr; +} TraitEffectLog; class MutationType : public EidosDictionaryUnretained { @@ -84,7 +91,38 @@ class MutationType : public EidosDictionaryUnretained private: EidosSymbolTableEntry self_symbol_; // for fast setup of the symbol table - + + // + // Mutation data logging: see MutationType::ExecuteMethod_logMutationData() + // + bool mutation_logging_on_ = false; + bool log_autogeneratedOnly_; + bool log_meanOnly_; + + bool log_id_, log_mutationTypeID_, log_chromosomeID_, log_position_, log_nucleotideValue_, log_originTick_, + log_subpopID_, log_tag_, log_effect_, log_dominance_, log_hemizygousDominance_; + + size_t log_size_ = 0; // number of entries logged + size_t log_capacity_ = 0; // number of entries allocated + + double running_id_, running_muttype_id_, running_chromosome_id_, running_position_, running_nucleotide_, + running_origin_tick_, running_subpop_id_, running_tag_; + + slim_mutationid_t *logged_id_ = nullptr; + slim_objectid_t *logged_muttype_id_ = nullptr; + int64_t *logged_chromosome_id_ = nullptr; + slim_position_t *logged_position_ = nullptr; + int8_t *logged_nucleotide_ = nullptr; + slim_tick_t *logged_origin_tick_ = nullptr; + slim_objectid_t *logged_subpop_id_ = nullptr; + slim_usertag_t *logged_tag_ = nullptr; + + std::vector logged_trait_indices; + std::vector logged_traits_; + + // This adds p_mut to the logs above, as selected by the user + void _LogMutationInfo(Mutation *p_mut); + public: // a mutation type is specified by the distribution of effects (DE) and the default dominance coefficient @@ -188,6 +226,13 @@ class MutationType : public EidosDictionaryUnretained #endif ~MutationType(void); + // mutation data logging + void FreeLoggingInfo(void); + inline __attribute__((always_inline)) bool LoggingOn(void) const { return mutation_logging_on_; } + inline __attribute__((always_inline)) void LogMutationInfo_AUTOGENERATED(Mutation *p_mut) { _LogMutationInfo(p_mut); } + inline __attribute__((always_inline)) void LogMutationInfo_SCRIPTED(Mutation *p_mut) { if (!log_autogeneratedOnly_) _LogMutationInfo(p_mut); } + + static void ParseDESParameters(std::string &p_DES_type_string, const EidosValue_SP *const p_arguments, int p_argument_count, DESType *p_DES_type, std::vector *p_DES_parameters, std::vector *p_DES_strings); @@ -228,6 +273,8 @@ class MutationType : public EidosDictionaryUnretained EidosValue_SP ExecuteMethod_effectDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_loggedData(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_logMutationData(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setDefaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); diff --git a/core/population.cpp b/core/population.cpp index 126fde4e..b71e7f2d 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -3346,7 +3346,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h { #pragma omp critical (MutationRegistryAdd) { - MutationRegistryAdd(new_mut); + MutationRegistryAdd(new_mut, /* p_autogenerated */ true); } } @@ -3496,7 +3496,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h { #pragma omp critical (MutationRegistryAdd) { - MutationRegistryAdd(new_mut); + MutationRegistryAdd(new_mut, /* p_autogenerated */ true); } } @@ -3550,7 +3550,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h { #pragma omp critical (MutationRegistryAdd) { - MutationRegistryAdd(new_mut); + MutationRegistryAdd(new_mut, /* p_autogenerated */ true); } } @@ -3705,7 +3705,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h { #pragma omp critical (MutationRegistryAdd) { - MutationRegistryAdd(new_mut); + MutationRegistryAdd(new_mut, /* p_autogenerated */ true); } } @@ -3975,7 +3975,7 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha { #pragma omp critical (MutationRegistryAdd) { - MutationRegistryAdd(new_mut); + MutationRegistryAdd(new_mut, /* p_autogenerated */ true); } } @@ -4435,7 +4435,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil { #pragma omp critical (MutationRegistryAdd) { - MutationRegistryAdd(new_mut); + MutationRegistryAdd(new_mut, /* p_autogenerated */ true); } } @@ -4489,7 +4489,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil { #pragma omp critical (MutationRegistryAdd) { - MutationRegistryAdd(new_mut); + MutationRegistryAdd(new_mut, /* p_autogenerated */ true); } } @@ -4644,7 +4644,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil { #pragma omp critical (MutationRegistryAdd) { - MutationRegistryAdd(new_mut); + MutationRegistryAdd(new_mut, /* p_autogenerated */ true); } } @@ -6132,7 +6132,7 @@ void Population::JoinMutationRunsForChromosome(int32_t p_new_mutrun_count, Chrom } #endif -void Population::MutationRegistryAdd(Mutation *p_mutation) +void Population::MutationRegistryAdd(Mutation *p_mutation, bool p_autogenerated) { #if DEBUG if ((p_mutation->state_ == MutationState::kInRegistry) || @@ -6151,6 +6151,16 @@ void Population::MutationRegistryAdd(Mutation *p_mutation) p_mutation->state_ = MutationState::kInRegistry; + MutationType *muttype = p_mutation->mutation_type_ptr_; + + if (muttype->LoggingOn()) + { + if (p_autogenerated) + muttype->LogMutationInfo_AUTOGENERATED(p_mutation); + else + muttype->LogMutationInfo_SCRIPTED(p_mutation); + } + #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES if (keeping_muttype_registries_) { diff --git a/core/population.h b/core/population.h index 64c49a18..16655a83 100644 --- a/core/population.h +++ b/core/population.h @@ -188,7 +188,7 @@ class Population return mutation_registry_.begin_pointer_const(); } - void MutationRegistryAdd(Mutation *p_mutation); + void MutationRegistryAdd(Mutation *p_mutation, bool p_autogenerated); // apply modifyChild() callbacks to a generated child; a return of false means "do not use this child, generate a new one" bool ApplyModifyChildCallbacks(Individual *p_child, Individual *p_parent1, Individual *p_parent2, bool p_is_selfing, bool p_is_cloning, Subpopulation *p_target_subpop, Subpopulation *p_source_subpop, std::vector &p_modify_child_callbacks); diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 94480ce8..7ef223a7 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1417,6 +1417,8 @@ const std::string &gStr_setDominanceForTrait = EidosRegisteredString("setDominan const std::string &gStr_setHemizygousDominanceForTrait = EidosRegisteredString("setHemizygousDominanceForTrait", gID_setHemizygousDominanceForTrait); const std::string &gStr_setMutationType = EidosRegisteredString("setMutationType", gID_setMutationType); const std::string &gStr_drawEffectForTrait = EidosRegisteredString("drawEffectForTrait", gID_drawEffectForTrait); +const std::string &gStr_loggedData = EidosRegisteredString("loggedData", gID_loggedData); +const std::string &gStr_logMutationData = EidosRegisteredString("logMutationData", gID_logMutationData); const std::string &gStr_setDefaultDominanceForTrait = EidosRegisteredString("setDefaultDominanceForTrait", gID_setDefaultDominanceForTrait); const std::string &gStr_setDefaultHemizygousDominanceForTrait = EidosRegisteredString("setDefaultHemizygousDominanceForTrait", gID_setDefaultHemizygousDominanceForTrait); const std::string &gStr_setEffectDistributionForTrait = EidosRegisteredString("setEffectDistributionForTrait", gID_setEffectDistributionForTrait); diff --git a/core/slim_globals.h b/core/slim_globals.h index 7be2db0e..18135187 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -988,6 +988,8 @@ extern const std::string &gStr_setDominanceForTrait; extern const std::string &gStr_setHemizygousDominanceForTrait; extern const std::string &gStr_setMutationType; extern const std::string &gStr_drawEffectForTrait; +extern const std::string &gStr_loggedData; +extern const std::string &gStr_logMutationData; extern const std::string &gStr_setDefaultDominanceForTrait; extern const std::string &gStr_setDefaultHemizygousDominanceForTrait; extern const std::string &gStr_setEffectDistributionForTrait; @@ -1479,6 +1481,8 @@ enum _SLiMGlobalStringID : int { gID_setHemizygousDominanceForTrait, gID_setMutationType, gID_drawEffectForTrait, + gID_loggedData, + gID_logMutationData, gID_setDefaultDominanceForTrait, gID_setDefaultHemizygousDominanceForTrait, gID_setEffectDistributionForTrait, diff --git a/core/species.cpp b/core/species.cpp index a60c440e..503a02d9 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1546,7 +1546,7 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry mutations.emplace(polymorphism_id, new_mut_index); - population_.MutationRegistryAdd(new_mut); + population_.MutationRegistryAdd(new_mut, /* p_autogenerated */ false); #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES if (population_.keeping_muttype_registries_) @@ -2306,7 +2306,7 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid // add it to our local map, so we can find it when making haplosomes, and to the population's mutation registry mutations[polymorphism_id] = new_mut_index; - population_.MutationRegistryAdd(new_mut); + population_.MutationRegistryAdd(new_mut, /* p_autogenerated */ false); #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES if (population_.keeping_muttype_registries_) @@ -10140,7 +10140,7 @@ void Species::__CreateMutationsFromTabulation(std::unordered_map &p_arg if (x_count == 0) { - result_SP = gStaticEidosValueNULL; + // BCH 1/11/2026: changing this case from NULL to NAN, to match R; + // I just noticed this, and I don't think there's any reason for it + result_SP = gStaticEidosValue_FloatNAN; } else if (x_count == 1) { @@ -1549,32 +1551,22 @@ EidosValue_SP Eidos_ExecuteFunction_sd(const std::vector &p_argum { // Note that this function ignores matrix/array attributes, and always returns a vector, by design // This is different from the behavior of var(), cor(), and cov(), but follows R - EidosValue_SP result_SP(nullptr); - EidosValue *x_value = p_arguments[0].get(); int x_count = x_value->Count(); if (x_count <= 1) return gStaticEidosValue_FloatNAN; - double mean = 0; - double sd = 0; - - for (int value_index = 0; value_index < x_count; ++value_index) - mean += x_value->NumericAtIndex_NOCAST(value_index, nullptr); - - mean /= x_count; + double sd; - for (int value_index = 0; value_index < x_count; ++value_index) - { - double temp = (x_value->NumericAtIndex_NOCAST(value_index, nullptr) - mean); - sd += temp * temp; - } - - sd = sqrt(sd / (x_count - 1)); - result_SP = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(sd)); + if (x_value->Type() == EidosValueType::kValueFloat) + sd = Eidos_StandardDeviation(x_value->FloatData(), x_count); + else if (x_value->Type() == EidosValueType::kValueInt) + sd = Eidos_StandardDeviation(x_value->IntData(), x_count); + else // EidosValueType::kValueLogical + sd = Eidos_StandardDeviation(x_value->LogicalData(), x_count); - return result_SP; + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(sd)); } // (float$)ttest(float x, [Nf y = NULL], [Nf$ mu = NULL]) diff --git a/eidos/eidos_globals.h b/eidos/eidos_globals.h index 8e11de05..dc2a47bc 100644 --- a/eidos/eidos_globals.h +++ b/eidos/eidos_globals.h @@ -660,6 +660,30 @@ void Eidos_WriteToFile(const std::string &p_file_path, const std::vector +double Eidos_StandardDeviation(T *x, size_t count) +{ + if (count < 2) + return std::numeric_limits::quiet_NaN(); + + double mean = 0; + double sd = 0; + + for (size_t value_index = 0; value_index < count; ++value_index) + mean += (double)x[value_index]; + + mean /= count; + + for (size_t value_index = 0; value_index < count; ++value_index) + { + double temp = (double)x[value_index] - mean; + sd += temp * temp; + } + + return sqrt(sd / (count - 1)); // note: sample sd, not population sd +} + // Correlation between two vectors x and y of equal length; int64_t or double are used, and can be mixed template double Eidos_Correlation(T1 *x, T2 *y, size_t count) diff --git a/eidos/eidos_test_functions_statistics.cpp b/eidos/eidos_test_functions_statistics.cpp index 6b1c80e8..46a12932 100644 --- a/eidos/eidos_test_functions_statistics.cpp +++ b/eidos/eidos_test_functions_statistics.cpp @@ -190,9 +190,9 @@ void _RunFunctionStatisticsTests_a_through_p(void) EidosAssertScriptRaise("mean(c('foo', 'bar', 'baz'));", 0, "cannot be type"); EidosAssertScriptRaise("mean(_Test(7));", 0, "cannot be type"); EidosAssertScriptRaise("mean(NULL);", 0, "cannot be type"); - EidosAssertScriptSuccess_NULL("mean(logical(0));"); - EidosAssertScriptSuccess_NULL("mean(integer(0));"); - EidosAssertScriptSuccess_NULL("mean(float(0));"); + EidosAssertScriptSuccess_F("mean(logical(0));", std::numeric_limits::quiet_NaN()); // BCH 1/11/2026: changed from NULL to NAN after SLiM 5.1 + EidosAssertScriptSuccess_F("mean(integer(0));", std::numeric_limits::quiet_NaN()); // BCH 1/11/2026: changed from NULL to NAN after SLiM 5.1 + EidosAssertScriptSuccess_F("mean(float(0));", std::numeric_limits::quiet_NaN()); // BCH 1/11/2026: changed from NULL to NAN after SLiM 5.1 EidosAssertScriptRaise("mean(string(0));", 0, "cannot be type"); EidosAssertScriptSuccess_F("mean(rep(1e18, 9));", 1e18); // stays in integer internally #if EIDOS_HAS_OVERFLOW_BUILTINS From df9dd54f34ffe4e43933a548261729054f6decb5 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 12 Jan 2026 10:26:34 -0600 Subject: [PATCH 075/107] a bit of cleanup for loggedData() --- QtSLiM/help/SLiMHelpClasses.html | 6 +++--- SLiMgui/SLiMHelpClasses.rtf | 32 +++++++++++++++++--------------- VERSIONS | 2 +- core/mutation_type.cpp | 19 +++++++++++++++---- core/slim_test_genetics.cpp | 24 ++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 0f70526d..a392e14c 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -800,9 +800,9 @@

Returns the parameters that configure the distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DES type "s", and type float for all other DES types.

– (string)effectDistributionTypeForTrait([Niso<Trait> trait = NULL])

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

-

– (fo<DataFrame>)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

-

Returns mutation data produced by the mutation type’s logging facility, as configured by logMutationData().  The data returned can be in the form of means across all logged mutations (for kind="mean"), standard deviations across all logged mutations (for kind="sd"), or separate values for each mutation (for kind="values").  If logging only of means was enabled (with the meanOnly=T option to logMutationData()), only kind="mean" is allowed, since separate values for each mutation are then not logged.

-

The remaining flags control which columns of data should be returned; see logMutationData() for a summary of the mutation properties they refer to.  If only one data column is specified by the flags, a vector of values (or a mean or standard deviation) will be returned for that one specified data column.  If more than one flag is set to T, a DataFrame object will be returned with named columns of values (or means, or standard deviations) for each specified data column.  The trait property specifies which traits values should be returned for, with respect to the effect, dominance, and hemizygousDominance flags; see logMutationData() for further description.  If all of these flags are F (the default), that is taken to mean that all logged data should be returned; in that case, a vector will be returned if only one column of data was logged, otherwise a DataFrame object will be returned, the same as when flags are specified explicitly.  Flags set to T for data columns that were not actually logged will simply be ignored; similarly, traits specified by trait that were not actually logged will simply be ignored.  See the Eidos manual for the DataFrame class documentation.

+

– (ifo<DataFrame>)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

+

Returns mutation data produced by the mutation type’s logging facility, as configured by logMutationData().  The data returned can be in the form of means across all logged mutations (for kind="mean"), standard deviations across all logged mutations (for kind="sd"), or separate values for each mutation (for kind="values"); kind="count" is also allowed, and simply returns a singleton integer providing the number of entries (i.e., the number of mutations) that have been recorded.  If logging only of means was enabled (with the meanOnly=T option to logMutationData()), only kind="mean" and kind="count" are allowed, since separate values for each mutation are then not logged.

+

The remaining flags control which columns of data should be returned (for kind options other than "count"); see logMutationData() for a summary of the mutation properties they refer to.  If only one data column is specified by the flags, a vector of values (or a mean or standard deviation) will be returned for that one specified data column.  If more than one flag is set to T, a DataFrame object will be returned with named columns of values (or means, or standard deviations) for each specified data column.  The trait property specifies which traits values should be returned for, with respect to the effect, dominance, and hemizygousDominance flags; see logMutationData() for further description.  Flags set to T for data columns that were not actually logged will simply be ignored; similarly, traits specified by trait that were not actually logged will simply be ignored.  See the Eidos manual for the DataFrame class documentation.

– (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

Starts or ends logging of data about new mutations belonging to the target mutation type.  If autogeneratedOnly is T (the default), only new mutations generated automatically by SLiM will be logged (including mutations that are substituted in for an auto-generated mutation using a mutation() callback; that is still considered part of the auto-generation process).  If autogeneratedOnly is F, mutations generated in script, such as with addNewMutation(), addNewDrawnMutation(), and reading from files such as VCF, MS, or .trees, will also be logged.  The logged information can be obtained later with the loggedData() method.  Once logging has been started with enable=T it cannot be modified, only stopped with enable=F; and if logging is subsequently resumed with enable=T, any previously logged data will be discarded.  (This can be useful if you wish to limit the size of the in-memory data while continuing to log new data: periodically write the accumulated data to a file and then disable and re-enable logging to discard the old data.)

If meanOnly is F (the default), values for each new mutation will be kept separately.  Beware: the memory usage entailed by this option can be extremely large!  Alternatively, if meanOnly is T, only a running sum, used to compute a mean, will be kept for each type of data; the memory usage for this option will be small and constant, but of course a mean is more useful for some columns of data than others.  If per-mutation data is desired for any one column, use meanOnly=F; this option cannot be controlled independently for the various columns of data being logged.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index b49dec1d..cb8b9656 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -7015,7 +7015,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 class documentation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(fo)loggedData(string$\'a0kind, [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effect\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ +\f3\fs18 \cf2 \'96\'a0(ifo)loggedData(string$\'a0kind, [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effect\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns mutation data produced by the mutation type\'92s logging facility, as configured by @@ -7026,14 +7026,25 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f3\fs18 kind="sd" \f4\fs20 ), or separate values for each mutation (for \f3\fs18 kind="values" -\f4\fs20 ). If logging only of means was enabled (with the +\f4\fs20 ); +\f3\fs18 kind="count" +\f4\fs20 is also allowed, and simply returns a singleton +\f3\fs18 integer +\f4\fs20 providing the number of entries (i.e., the number of mutations) that have been recorded. If logging only of means was enabled (with the \f3\fs18 meanOnly=T \f4\fs20 option to \f3\fs18 logMutationData() \f4\fs20 ), only \f3\fs18 kind="mean" -\f4\fs20 is allowed, since separate values for each mutation are then not logged.\ -The remaining flags control which columns of data should be returned; see +\f4\fs20 and +\f3\fs18 kind="count" +\f4\fs20 are allowed, since separate values for each mutation are then not logged.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 The remaining flags control which columns of data should be returned (for +\f3\fs18 kind +\f4\fs20 options other than +\f3\fs18 "count" +\f4\fs20 ); see \f3\fs18 logMutationData() \f4\fs20 for a summary of the mutation properties they refer to. If only one data column is specified by the flags, a vector of values (or a mean or standard deviation) will be returned for that one specified data column. If more than one flag is set to \f3\fs18 T @@ -7049,15 +7060,7 @@ The remaining flags control which columns of data should be returned; see \f3\fs18 hemizygousDominance \f4\fs20 flags; see \f3\fs18 logMutationData() -\f4\fs20 for further description. If -\f1\i all -\f4\i0 of these flags are -\f3\fs18 F -\f4\fs20 (the default), that is taken to mean that -\f1\i all -\f4\i0 logged data should be returned; in that case, a vector will be returned if only one column of data was logged, otherwise a -\f3\fs18 DataFrame -\f4\fs20 object will be returned, the same as when flags are specified explicitly. Flags set to +\f4\fs20 for further description. Flags set to \f3\fs18 T \f4\fs20 for data columns that were not actually logged will simply be ignored; similarly, traits specified by \f3\fs18 trait @@ -7094,8 +7097,7 @@ The remaining flags control which columns of data should be returned; see \f4\fs20 ; and if logging is subsequently resumed with \f3\fs18 enable=T \f4\fs20 , any previously logged data will be discarded. (This can be useful if you wish to limit the size of the in-memory data while continuing to log new data: periodically write the accumulated data to a file and then disable and re-enable logging to discard the old data.)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 If +If \f3\fs18 meanOnly \f4\fs20 is \f3\fs18 F diff --git a/VERSIONS b/VERSIONS index dfa05f0f..c08c843b 100644 --- a/VERSIONS +++ b/VERSIONS @@ -138,7 +138,7 @@ multitrait branch: add a Species demandPhenotype() method, and change the Individual method demandPhenotype() to demandPhenotypeForIndividuals(); the Species method is generally preferred for speed add data logging capabilities to MutationType for easier assessment of requested vs. realized DFEs, etc. - (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], ) - - (fo)loggedData(string$ kind, ) + - (ifo)loggedData(string$ kind, ) policy change: mean(integer(0)) or mean(float(0)) now returns NAN rather than NULL, to match R; I just noticed this, and NAN does seem better diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 278f874f..0c9ee903 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -1053,7 +1053,8 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho // kind typedef enum class _KindEnum { - kMean = 1, + kCount = 0, + kMean, kSD, kValues } KindEnum; @@ -1062,15 +1063,22 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho const std::string &kind_str = kind_value_string->StringRefAtIndex_NOCAST(0, nullptr); KindEnum kind; - if (kind_str == "mean") kind = KindEnum::kMean; + if (kind_str == "count") kind = KindEnum::kCount; + else if (kind_str == "mean") kind = KindEnum::kMean; else if (kind_str == "sd") kind = KindEnum::kSD; else if (kind_str == "values") kind = KindEnum::kValues; else - EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_loggedData): loggedData() requires that kind be 'mean', 'sd', or 'values'." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_loggedData): loggedData() requires that kind be 'count', 'mean', 'sd', or 'values'." << EidosTerminate(nullptr); if (log_meanOnly_ && (kind != KindEnum::kMean)) EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_loggedData): loggedData() can only return means (kind='mean'), since meanOnly=T was set in logMutationData()." << EidosTerminate(nullptr); + if (kind == KindEnum::kCount) + { + // for "count" the remaining flags are ignored; we just return the singleton count of mutations recorded + return EidosValue_SP(new EidosValue_Int((int64_t)log_size_)); + } + // trait std::vector trait_indices; species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "loggedData"); @@ -1138,6 +1146,9 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho (int)get_nucleotideValue + (int)get_originTick + (int)get_subpopID + (int)get_tag + ((int)get_effect) * trait_count + ((int)get_dominance) * trait_count + ((int)get_hemizygousDominance) * trait_count; + if (requested_count == 0) + return gStaticEidosValueNULL; + EidosDataFrame *dataframe = nullptr; EidosValue_SP result_SP; @@ -1613,7 +1624,7 @@ const std::vector *MutationType_Class::Methods(void) c methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_loggedData, kEidosValueMaskFloat | kEidosValueMaskObject, gEidosDataFrame_Class)) + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_loggedData, kEidosValueMaskInt | kEidosValueMaskFloat | kEidosValueMaskObject, gEidosDataFrame_Class)) ->AddString_S("kind") ->AddLogical_OS("id", gStaticEidosValue_LogicalF) ->AddLogical_OS("mutationTypeID", gStaticEidosValue_LogicalF) diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index d9152436..e4c62d24 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -146,6 +146,30 @@ void _RunMutationTypeTests(void) SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 2.910106) < 0.1) stop(); }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); m1.drawEffectForTrait(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 2.0) < 0.1) stop(); }", __LINE__); + + // test mutation date recording with logMutationData() and loggedData() + std::string data_recording = + R"V0G0N( + initialize() { + initializeMutationRate(1e-7); + initializeMutationType("m1", 0.5, "f", 0.0); + m1.logMutationData(T, chromosomeID=T, position=T); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 999999); + initializeRecombinationRate(1e-8); + } + 1 early() { sim.addSubpop("p1", 50); } + 100 late() { + count = m1.loggedData(kind="count"); + pos = m1.loggedData(kind="values", position=T); + df = m1.loggedData(kind="values", chromosomeID=T, position=T); + + if ((count == 0) | (count != length(pos)) | (count != df.nrow)) + stop("MutationType data recording problem"); + } + )V0G0N"; + + SLiMAssertScriptSuccess(data_recording); } #pragma mark GenomicElementType tests From b8f35d9b28930ef9901d1342cf8217737ae07ea4 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 12 Jan 2026 11:43:24 -0600 Subject: [PATCH 076/107] fix crash in new code, uncovered by CI --- core/mutation_type.cpp | 74 +++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 0c9ee903..bc42c779 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -243,17 +243,17 @@ void MutationType::_LogMutationInfo(Mutation *p_mut) if (log_effect_) { slim_effect_t effect = mut_trait_info.effect_size_; - trait_log.running_effect_ += effect; + trait_log.running_effect_ += (double)effect; if (!log_meanOnly_) trait_log.logged_effect_[log_size_] = effect; } if (log_dominance_) { slim_effect_t dominance = p_mut->RealizedDominanceForTrait(species_.Traits()[trait_index]); - trait_log.running_dominance_ += dominance; + trait_log.running_dominance_ += (double)dominance; if (!log_meanOnly_) trait_log.logged_dominance_[log_size_] = dominance; } if (log_hemizygousDominance_) { slim_effect_t hemizygousDominance = mut_trait_info.hemizygous_dominance_coeff_; - trait_log.running_hemizygous_dominance_ += hemizygousDominance; + trait_log.running_hemizygous_dominance_ += (double)hemizygousDominance; if (!log_meanOnly_) trait_log.logged_hemizygous_dominance_[log_size_] = hemizygousDominance; } } @@ -1076,7 +1076,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (kind == KindEnum::kCount) { // for "count" the remaining flags are ignored; we just return the singleton count of mutations recorded - return EidosValue_SP(new EidosValue_Int((int64_t)log_size_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int((int64_t)log_size_)); } // trait @@ -1166,10 +1166,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_id) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_id_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(running_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(logged_id_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Int())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(log_size_); int64_t *column_data = column->IntData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_id_[log_index]; @@ -1180,10 +1180,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_mutationTypeID) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_muttype_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_muttype_id_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(running_muttype_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(logged_muttype_id_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Int())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(log_size_); int64_t *column_data = column->IntData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_muttype_id_[log_index]; @@ -1194,10 +1194,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_chromosomeID) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_chromosome_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_chromosome_id_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(running_chromosome_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(logged_chromosome_id_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Int())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(log_size_); int64_t *column_data = column->IntData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_chromosome_id_[log_index]; @@ -1208,10 +1208,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_position) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_position_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_position_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(running_position_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(logged_position_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Int())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(log_size_); int64_t *column_data = column->IntData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_position_[log_index]; @@ -1222,10 +1222,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_nucleotideValue) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_nucleotide_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_nucleotide_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(running_nucleotide_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(logged_nucleotide_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Int())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(log_size_); int64_t *column_data = column->IntData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_nucleotide_[log_index]; @@ -1236,10 +1236,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_originTick) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_origin_tick_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_origin_tick_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(running_origin_tick_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(logged_origin_tick_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Int())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(log_size_); int64_t *column_data = column->IntData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_origin_tick_[log_index]; @@ -1250,10 +1250,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_subpopID) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_subpop_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_subpop_id_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(running_subpop_id_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(logged_subpop_id_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Int())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(log_size_); int64_t *column_data = column->IntData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_subpop_id_[log_index]; @@ -1264,10 +1264,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_tag) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(running_tag_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(logged_tag_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(running_tag_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(logged_tag_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Int())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(log_size_); int64_t *column_data = column->IntData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_tag_[log_index]; @@ -1283,10 +1283,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_effect) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(trait_log.running_effect_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_effect_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_log.running_effect_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_effect_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Float())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(log_size_); double *column_data = column->FloatData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = (double)trait_log.logged_effect_[log_index]; @@ -1297,10 +1297,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_dominance) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(trait_log.running_dominance_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_dominance_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_log.running_dominance_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_dominance_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Float())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(log_size_); double *column_data = column->FloatData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = (double)trait_log.logged_dominance_[log_index]; @@ -1311,10 +1311,10 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (get_hemizygousDominance) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new EidosValue_Float(trait_log.running_hemizygous_dominance_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_hemizygous_dominance_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_log.running_hemizygous_dominance_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_hemizygous_dominance_, log_size_)); else /* kind == KindEnum::kValues */ { - column = (new EidosValue_Float())->resize_no_initialize(log_size_); + column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(log_size_); double *column_data = column->FloatData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = (double)trait_log.logged_hemizygous_dominance_[log_index]; From 8b8d48e0bee1ffe8cfe0259fa01f34b4dd72ede2 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 12 Jan 2026 18:33:29 -0600 Subject: [PATCH 077/107] extend mutationEffect() callbacks with multi-trait frills --- QtSLiM/QtSLiMScriptTextEdit.cpp | 3 +- QtSLiM/help/SLiMHelpClasses.html | 8 +- SLiMgui/SLiMHelpClasses.rtf | 39 +++++--- SLiMgui/SLiMWindowController.mm | 3 +- VERSIONS | 5 + core/haplosome.cpp | 12 +-- core/individual.cpp | 156 +++++++++++++++---------------- core/individual.h | 4 +- core/mutation.cpp | 14 +-- core/mutation_type.cpp | 20 ++-- core/slim_eidos_block.cpp | 67 +++++++++++-- core/slim_eidos_block.h | 4 +- core/slim_globals.cpp | 2 + core/slim_globals.h | 2 + core/slim_test_core.cpp | 7 +- core/species.cpp | 90 +++++++++++++++--- core/species_eidos.cpp | 24 +++-- core/subpopulation.cpp | 87 +++++++++++------ core/subpopulation.h | 8 +- core/substitution.cpp | 8 +- 20 files changed, 366 insertions(+), 197 deletions(-) diff --git a/QtSLiM/QtSLiMScriptTextEdit.cpp b/QtSLiM/QtSLiMScriptTextEdit.cpp index 07b3c95e..d98ed060 100644 --- a/QtSLiM/QtSLiMScriptTextEdit.cpp +++ b/QtSLiM/QtSLiMScriptTextEdit.cpp @@ -979,7 +979,7 @@ void QtSLiMTextEdit::updateStatusFieldFromSelection(void) else if (callName == "mutationEffect") { static EidosCallSignature_CSP callbackSig = nullptr; - if (!callbackSig) callbackSig = EidosCallSignature_CSP((new EidosFunctionSignature("mutationEffect", nullptr, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddObject_S("mutationType", gSLiM_MutationType_Class)->AddObject_OS("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULLInvisible)); + if (!callbackSig) callbackSig = EidosCallSignature_CSP((new EidosFunctionSignature("mutationEffect", nullptr, kEidosValueMaskNULL | kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddObject_S("mutationType", gSLiM_MutationType_Class)->AddObject_OS("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULLInvisible)->AddString_OS("trait", gStaticEidosValueNULLInvisible)); signature = callbackSig; } else if (callName == "fitnessEffect") @@ -2129,6 +2129,7 @@ void QtSLiMTextEdit::slimSpecificCompletion(QString completionScriptString, NSRa case SLiMEidosBlockType::SLiMEidosMutationEffectCallback: (*typeTable)->SetTypeForSymbol(gID_mut, EidosTypeSpecifier{kEidosValueMaskObject, gSLiM_Mutation_Class}); (*typeTable)->SetTypeForSymbol(gID_homozygous, EidosTypeSpecifier{kEidosValueMaskLogical, nullptr}); + (*typeTable)->SetTypeForSymbol(gID_trait, EidosTypeSpecifier{kEidosValueMaskObject, gSLiM_Trait_Class}); (*typeTable)->SetTypeForSymbol(gID_effect, EidosTypeSpecifier{kEidosValueMaskFloat, nullptr}); (*typeTable)->SetTypeForSymbol(gID_individual, EidosTypeSpecifier{kEidosValueMaskObject, gSLiM_Individual_Class}); (*typeTable)->SetTypeForSymbol(gID_subpop, EidosTypeSpecifier{kEidosValueMaskObject, gSLiM_Subpopulation_Class}); diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index a392e14c..b2803b28 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -340,7 +340,7 @@

Removing mutations will normally affect the fitness values calculated at the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

The optional parameter substitute was added in SLiM 2.2, with a default of F for backward compatibility.  If substitute is T, Substitution objects will be created for all of the removed mutations so that they are recorded in the simulation as having fixed, just as if they had reached fixation and been removed by SLiM’s own internal machinery.  This will occur regardless of whether the mutations have in fact fixed, regardless of the convertToSubstitution property of the relevant mutation types, and regardless of whether all copies of the mutations have even been removed from the simulation (making it possible to create Substitution objects for mutations that are still segregating).  It is up to the caller to perform whatever checks are necessary to preserve the integrity of the simulation’s records.  Typically substitute will only be set to T in the context of calls like sim.subpopulations.haplosomes.removeMutations(muts, T), such that the substituted mutations are guaranteed to be entirely removed from circulation.  As mentioned above, substitute may not be T if mutations is NULL.

 (float$)sumOfMutationsOfType(io<MutationType>$ mutType, [Niso<Trait>$ trait = NULL])

-

Returns the sum of the effects of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosome, for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL can be used to represent the one defined trait in a single-trait species.

+

Returns the sum of the effects of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosome, for the trait specified by trait.  The trait can be specified as the integer index or string name of a trait in the species, or directly as a Trait object; NULL can be used to represent the one defined trait in a single-trait species.

Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, sumOfMutationsOfType() provides the sum of the additive effects of the QTLs for the given mutation type.  With the support for quantitative traits that is now built into SLiM this method has largely been superseded, and is now provided mostly for backward compatibility.  This method is provided for speed; it is much faster than the corresponding Eidos code.  Note that this method also exists on Individual, for cases in which the sum across both haplosomes of an individual is desired.

5.5  Class GenomicElement

5.5.1  GenomicElement properties

@@ -531,7 +531,7 @@

AA AA 2 (full siblings)

This method does not estimate consanguinity.  For example, if one individual is itself a parent of the other individual, that is irrelevant for this method.  Similarly, in simulations of sex chromosomes, the sexes of the parents are irrelevant, even if no genetic material would have been inherited from a given parent.  See relatedness() for an assessment of pedigree-based relatedness that does estimate the consanguinity of individuals.  The sharedParentCount() method is preferable if your exact question is simply whether individuals are full siblings, half siblings, or non-siblings; in other cases, relatedness() is probably more useful.

 (float$)sumOfMutationsOfType(io<MutationType>$ mutType, [Niso<Trait>$ trait = NULL])

-

Returns the sum of the effects of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosomes of the individual, for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL can be used to represent the one defined trait in a single-trait species.

+

Returns the sum of the effects of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosomes of the individual, for the trait specified by trait.  The trait can be specified as the integer index or string name of a trait in the species, or directly as a Trait object; NULL can be used to represent the one defined trait in a single-trait species.

Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, sumOfMutationsOfType() provides the sum of the additive effects of the QTLs for the given mutation type.  With the support for quantitative traits that is now built into SLiM this method has largely been superseded, and is now provided mostly for backward compatibility.  This method is provided for speed; it is much faster than the corresponding Eidos code.  Note that this method also exists on Haplosome, for cases in which the sum for just one haplosome is desired.

 (object<Mutation>)uniqueMutationsOfType(io<MutationType>$ mutType)

This method has been deprecated, and may be removed in a future release of SLiM.  Its functionality was replaced by mutationsFromHaplosomes() in SLiM 5.0.

@@ -1143,8 +1143,8 @@

Register a block of Eidos source code, represented as the string singleton source, as an Eidos modifyChild() callback in the current simulation (specific to the target species), with optional subpopulation subpop (which may be an integer identifier, or NULL, the default, to indicate all subpopulations) and optional start and end ticks all limiting its applicability.  The script block will be given identifier id (specified as an integer, or as a string symbolic name such as "s5"); this may be NULL if there is no need to be able to refer to the block later.  The registered callback is added to the end of the list of registered SLiMEidosBlock objects, and is active immediately; it may be eligible to execute in the current tick.  The new SLiMEidosBlock will be defined as a global variable immediately by this method, and will also be returned by this method.

– (object<SLiMEidosBlock>$)registerMutationCallback(Nis$ id, string$ source, [Nio<MutationType>$ mutType = NULL], [Nio<Subpopulation>$ subpop = NULL], [Ni$ start = NULL], [Ni$ end = NULL])

Register a block of Eidos source code, represented as the string singleton source, as an Eidos mutation() callback in the current simulation (specific to the target species), with an optional mutation type mutType (which may be an integer mutation type identifier, or NULL, the default, to indicate all mutation types), optional subpopulation subpop (which may also be an integer identifier, or NULL, the default, to indicate all subpopulations), and optional start and end ticks all limiting its applicability.  The script block will be given identifier id (specified as an integer, or as a string symbolic name such as "s5"); this may be NULL if there is no need to be able to refer to the block later.  The registered callback is added to the end of the list of registered SLiMEidosBlock objects, and is active immediately; it may be eligible to execute in the current tick.  The new SLiMEidosBlock will be defined as a global variable immediately by this method, and will also be returned by this method.

-

– (object<SLiMEidosBlock>$)registerMutationEffectCallback(Nis$ id, string$ source, io<MutationType>$ mutType, [Nio<Subpopulation>$ subpop = NULL], [Ni$ start = NULL], [Ni$ end = NULL])

-

Register a block of Eidos source code, represented as the string singleton source, as an Eidos mutationEffect() callback in the current simulation (specific to the target species), with a required mutation type mutType (which may be an integer mutation type identifier), optional subpopulation subpop (which may also be an integer identifier, or NULL, the default, to indicate all subpopulations), and optional start and end ticks all limiting its applicability.  The script block will be given identifier id (specified as an integer, or as a string symbolic name such as "s5"); this may be NULL if there is no need to be able to refer to the block later.  The registered callback is added to the end of the list of registered SLiMEidosBlock objects, and is active immediately; it may be eligible to execute in the current tick.  The new SLiMEidosBlock will be defined as a global variable immediately by this method, and will also be returned by this method.

+

– (object<SLiMEidosBlock>$)registerMutationEffectCallback(Nis$ id, string$ source, io<MutationType>$ mutType, [Nio<Subpopulation>$ subpop = NULL], [Ni$ start = NULL], [Ni$ end = NULL], [Niso<Trait>$ trait = NULL])

+

Register a block of Eidos source code, represented as the string singleton source, as an Eidos mutationEffect() callback in the current simulation (specific to the target species), with a required mutation type mutType (which may be an integer mutation type identifier), optional subpopulation subpop (which may also be an integer identifier, or NULL, the default, to indicate all subpopulations), optional start and end ticks, and optional trait identifier trait (which may be an integer trait index, a string trait name, a Trait object, or NULL if the species has only one defined trait anyway), all limiting its applicability.  The script block will be given identifier id (specified as an integer, or as a string symbolic name such as "s5"); this may be NULL if there is no need to be able to refer to the block later.  The registered callback is added to the end of the list of registered SLiMEidosBlock objects, and is active immediately; it may be eligible to execute in the current tick.  The new SLiMEidosBlock will be defined as a global variable immediately by this method, and will also be returned by this method.

– (object<SLiMEidosBlock>$)registerRecombinationCallback(Nis$ id, string$ source, [Nio<Subpopulation>$ subpop = NULL], [Niso<Chromosome>$ chromosome = NULL], [Ni$ start = NULL], [Ni$ end = NULL])

Register a block of Eidos source code, represented as the string singleton source, as an Eidos recombination() callback in the current simulation (specific to the target species), with optional subpopulation subpop (which may be an integer identifier, or NULL, the default, to indicate all subpopulations) and optional start and end ticks all limiting its applicability.  In multi-chromosome models, parameter chromosome, if non-NULL, may specify a chromosome to which the callback will apply (as either an integer id, a string symbol, or a Chromosome object); otherwise, NULL indicates that the callback applies to all chromosomes.  The script block will be given identifier id (specified as an integer, or as a string symbolic name such as "s5"); this may be NULL if there is no need to be able to refer to the block later.  The registered callback is added to the end of the list of registered SLiMEidosBlock objects, and is active immediately; it may be eligible to execute in the current tick.  The new SLiMEidosBlock will be defined as a global variable immediately by this method, and will also be returned by this method.

– (object<SLiMEidosBlock>$)registerReproductionCallback(Nis$ id, string$ source, [Nio<Subpopulation>$ subpop = NULL], [Ns$ sex = NULL], [Ni$ start = NULL], [Ni$ end = NULL])

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index cb8b9656..3dd6c1d3 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -2721,15 +2721,15 @@ The optional parameter \f4\fs20 \cf2 Returns the sum of the effects of all mutations that are of the type specified by \f3\fs18 mutType -\f4\fs20 , out of all of the mutations in the haplosome, for the trait(s) specified by +\f4\fs20 , out of all of the mutations in the haplosome, for the trait specified by \f3\fs18 trait -\f4\fs20 . The traits can be specified as +\f4\fs20 . The trait can be specified as the \f3\fs18 integer -\f4\fs20 indices or +\f4\fs20 index or \f3\fs18 string -\f4\fs20 names of traits in the species, or directly as +\f4\fs20 name of a trait in the species, or directly as a \f3\fs18 Trait -\f4\fs20 objects; +\f4\fs20 object; \f3\fs18 NULL \f4\fs20 can be used to represent the one defined trait in a single-trait species.\ Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, @@ -4526,15 +4526,15 @@ More specifically, this method uses the parental pedigree IDs from the pedigree \f4\fs20 \cf2 Returns the sum of the effects of all mutations that are of the type specified by \f3\fs18 mutType -\f4\fs20 , out of all of the mutations in the haplosomes of the individual, for the trait(s) specified by +\f4\fs20 , out of all of the mutations in the haplosomes of the individual, for the trait specified by \f3\fs18 trait -\f4\fs20 . The traits can be specified as +\f4\fs20 . The trait can be specified as the \f3\fs18 integer -\f4\fs20 indices or +\f4\fs20 index or \f3\fs18 string -\f4\fs20 names of traits in the species, or directly as +\f4\fs20 name of a trait in the species, or directly as a \f3\fs18 Trait -\f4\fs20 objects; +\f4\fs20 object; \f3\fs18 NULL \f4\fs20 can be used to represent the one defined trait in a single-trait species.\ Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, @@ -7039,8 +7039,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 and \f3\fs18 kind="count" \f4\fs20 are allowed, since separate values for each mutation are then not logged.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 The remaining flags control which columns of data should be returned (for +The remaining flags control which columns of data should be returned (for \f3\fs18 kind \f4\fs20 options other than \f3\fs18 "count" @@ -11133,7 +11132,7 @@ After this call, the fitness values used for all purposes in SLiM will be the ne \f4\fs20 will be defined as a global variable immediately by this method, and will also be returned by this method.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \kerning1\expnd0\expndtw0 \'96\'a0(object$)registerMutationEffectCallback(Nis$\'a0id, string$\'a0source, io$\'a0mutType, [Nio$\'a0subpop\'a0=\'a0NULL], [Ni$\'a0start\'a0=\'a0NULL], [Ni$\'a0end\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \kerning1\expnd0\expndtw0 \'96\'a0(object$)registerMutationEffectCallback(Nis$\'a0id, string$\'a0source, io$\'a0mutType, [Nio$\'a0subpop\'a0=\'a0NULL], [Ni$\'a0start\'a0=\'a0NULL], [Ni$\'a0end\'a0=\'a0NULL], [Niso$\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Register a block of Eidos source code, represented as the @@ -11152,11 +11151,21 @@ After this call, the fitness values used for all purposes in SLiM will be the ne \f3\fs18 integer \f4\fs20 identifier, or \f3\fs18 NULL -\f4\fs20 , the default, to indicate all subpopulations), and optional +\f4\fs20 , the default, to indicate all subpopulations), optional \f3\fs18 start \f4\fs20 and \f3\fs18 end -\f4\fs20 ticks all limiting its applicability. The script block will be given identifier +\f4\fs20 ticks, and optional trait identifier +\f3\fs18 trait +\f4\fs20 (which may be an +\f3\fs18 integer +\f4\fs20 trait index, a +\f3\fs18 string +\f4\fs20 trait name, a +\f3\fs18 Trait +\f4\fs20 object, or +\f3\fs18 NULL +\f4\fs20 if the species has only one defined trait anyway), all limiting its applicability. The script block will be given identifier \f3\fs18 id \f4\fs20 (specified as an \f3\fs18 integer diff --git a/SLiMgui/SLiMWindowController.mm b/SLiMgui/SLiMWindowController.mm index 6fe77b10..2adcf1af 100644 --- a/SLiMgui/SLiMWindowController.mm +++ b/SLiMgui/SLiMWindowController.mm @@ -3828,6 +3828,7 @@ - (BOOL)eidosTextView:(EidosTextView *)eidosTextView completionContextWithScript case SLiMEidosBlockType::SLiMEidosMutationEffectCallback: (*typeTable)->SetTypeForSymbol(gID_mut, EidosTypeSpecifier{kEidosValueMaskObject, gSLiM_Mutation_Class}); (*typeTable)->SetTypeForSymbol(gID_homozygous, EidosTypeSpecifier{kEidosValueMaskLogical, nullptr}); + (*typeTable)->SetTypeForSymbol(gID_trait, EidosTypeSpecifier{kEidosValueMaskObject, gSLiM_Trait_Class}); (*typeTable)->SetTypeForSymbol(gID_effect, EidosTypeSpecifier{kEidosValueMaskFloat, nullptr}); (*typeTable)->SetTypeForSymbol(gID_individual, EidosTypeSpecifier{kEidosValueMaskObject, gSLiM_Individual_Class}); (*typeTable)->SetTypeForSymbol(gID_subpop, EidosTypeSpecifier{kEidosValueMaskObject, gSLiM_Subpopulation_Class}); @@ -4139,7 +4140,7 @@ - (void)updateStatusFieldFromSelection static EidosCallSignature_CSP callbackSig = nullptr; if (!callbackSig) - callbackSig = EidosCallSignature_CSP((new EidosFunctionSignature("mutationEffect", nullptr, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddObject_S("mutationType", gSLiM_MutationType_Class)->AddObject_OS("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULLInvisible)); + callbackSig = EidosCallSignature_CSP((new EidosFunctionSignature("mutationEffect", nullptr, kEidosValueMaskNULL | kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddObject_S("mutationType", gSLiM_MutationType_Class)->AddObject_OS("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULLInvisible)->AddString_OS("trait", gStaticEidosValueNULLInvisible)); sig = callbackSig.get(); } diff --git a/VERSIONS b/VERSIONS index c08c843b..aa8ce64b 100644 --- a/VERSIONS +++ b/VERSIONS @@ -140,6 +140,11 @@ multitrait branch: - (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], ) - (ifo)loggedData(string$ kind, ) policy change: mean(integer(0)) or mean(float(0)) now returns NAN rather than NULL, to match R; I just noticed this, and NAN does seem better + add optional [Ns$ trait = NULL] specifier to the mutationEffect() callback syntax, allowing mutationEffect() callbacks to be defined as specific to a particular trait + note that if this new specifier is absent or NULL, the callback will be called separately for each trait; that preserves backward compatibility + add trait pseudo-parameter to mutationEffect() callbacks + add [Niso$ trait = NULL] parameter to registerMutationEffectCallback() to allow the trait specifier to be given (defaults to NULL for backward compatibility) + add a new return option for mutationEffect() callbacks: NULL now indicates neutrality for the focal trait, meaning either 1.0 (multiplicative) or 0.0 (additive) version 5.1 (Eidos version 4.1): diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 3ffeacdf..f3cb8a88 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -1318,14 +1318,8 @@ EidosValue_SP Haplosome::ExecuteMethod_sumOfMutationsOfType(EidosGlobalStringID Species &species = individual_->subpopulation_->species_; MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species.community_, &species, "sumOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK - // get the trait indices, with bounds-checking - std::vector trait_indices; - species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "sumOfMutationsOfType"); - - if (trait_indices.size() != 1) - EIDOS_TERMINATION << "ERROR (Haplosome::ExecuteMethod_sumOfMutationsOfType): sumOfMutationsOfType() requires exactly one trait to be specified." << EidosTerminate(); - - const slim_trait_index_t trait_index = trait_indices[0]; + // get the trait index, with bounds-checking + const slim_trait_index_t trait_index = species.GetTraitIndexFromEidosValue(trait_value, "sumOfMutationsOfType"); // Sum the selection coefficients of mutations of the given type MutationBlock *mutation_block = species.SpeciesMutationBlock(); @@ -2291,7 +2285,7 @@ const std::vector *Haplosome_Class::Methods(void) cons methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_outputHaplosomesToMS, kEidosValueMaskVOID))->AddString_OSN(gEidosStr_filePath, gStaticEidosValueNULL)->AddLogical_OS("append", gStaticEidosValue_LogicalF)->AddLogical_OS("filterMonomorphic", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_outputHaplosomesToVCF, kEidosValueMaskVOID))->AddString_OSN(gEidosStr_filePath, gStaticEidosValueNULL)->AddLogical_OS("outputMultiallelics", gStaticEidosValue_LogicalT)->AddLogical_OS("append", gStaticEidosValue_LogicalF)->AddLogical_OS("simplifyNucleotides", gStaticEidosValue_LogicalF)->AddLogical_OS("outputNonnucleotides", gStaticEidosValue_LogicalT)->AddLogical_OS("groupAsIndividuals", gStaticEidosValue_LogicalT)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_outputHaplosomes, kEidosValueMaskVOID))->AddString_OSN(gEidosStr_filePath, gStaticEidosValueNULL)->AddLogical_OS("append", gStaticEidosValue_LogicalF)->AddLogical_OS("objectTags", gStaticEidosValue_LogicalF)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddIntStringObject_OSN("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddIntStringObject_OSN(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/individual.cpp b/core/individual.cpp index 5a7a1609..6c7e3126 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -3639,14 +3639,8 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb EidosValue *trait_value = p_arguments[1].get(); MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species->community_, species, "sumOfMutationsOfType()"); // SPECIES CONSISTENCY CHECK - // get the trait indices, with bounds-checking - std::vector trait_indices; - species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "sumOfMutationsOfType"); - - if (trait_indices.size() != 1) - EIDOS_TERMINATION << "ERROR (Haplosome::ExecuteMethod_sumOfMutationsOfType): sumOfMutationsOfType() requires exactly one trait to be specified." << EidosTerminate(); - - const slim_trait_index_t trait_index = trait_indices[0]; + // get the trait index, with bounds-checking + const slim_trait_index_t trait_index = species->GetTraitIndexFromEidosValue(trait_value, "sumOfMutationsOfType"); // Sum the selection coefficients of mutations of the given type MutationBlock *mutation_block = species->SpeciesMutationBlock(); @@ -4374,13 +4368,13 @@ const std::vector *Individual_Class::Methods(void) con methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_countOfMutationsOfType, kEidosValueMaskInt | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_countOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_relatedness, kEidosValueMaskFloat))->AddObject("individuals", gSLiM_Individual_Class)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, "chromosome", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_haplosomesForChromosomes, kEidosValueMaskObject, gSLiM_Haplosome_Class))->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddInt_OSN("index", gStaticEidosValueNULL)->AddLogical_OS("includeNulls", gStaticEidosValue_LogicalT)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_phenotypeForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_demandPhenotypeForIndividuals, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setPhenotypeForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddNumeric("phenotype")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_offsetForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_phenotypeForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_demandPhenotypeForIndividuals, kEidosValueMaskVOID))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setOffsetForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("offset", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setPhenotypeForTrait, kEidosValueMaskVOID))->AddIntStringObject_N(gStr_trait, gSLiM_Trait_Class)->AddNumeric("phenotype")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sharedParentCount, kEidosValueMaskInt))->AddObject("individuals", gSLiM_Individual_Class)); - methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddIntStringObject_OSN("trait", gSLiM_Trait_Class, gStaticEidosValueNULL))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); + methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_sumOfMutationsOfType, kEidosValueMaskFloat | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddIntStringObject_OSN(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL))->DeclareAcceleratedImp(Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_uniqueMutationsOfType, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->MarkDeprecated()); methods->emplace_back(((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_zygosityOfMutations, kEidosValueMaskInt))->AddObject_ON("mutations", gSLiM_Mutation_Class, gStaticEidosValueNULL)->AddInt_OS("hemizygousValue", gStaticEidosValue_Integer1)->AddInt_OS("haploidValue", gStaticEidosValue_Integer1))); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_mutationsFromHaplosomes, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddString_S("category")->AddIntObject_OSN("mutType", gSLiM_MutationType_Class, gStaticEidosValueNULL)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional, "chromosomes", gSLiM_Chromosome_Class, gStaticEidosValueNULL)); @@ -6703,6 +6697,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; #if DEBUG_TRAIT_DEMAND std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->symbol_ << "'" << std::endl; @@ -6727,7 +6722,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual { // hemizygous (haplosome2) auto IncorporateEffects_Hemizygous_TEMPLATED = subpop_trait_caches.IncorporateEffects_Hemizygous_TEMPLATED; - (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome2, trait_index, subpop_trait_mutationEffect_callbacks); + (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome2, trait, subpop_trait_mutationEffect_callbacks); } else { @@ -6738,7 +6733,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual { // hemizygous (haplosome1) auto IncorporateEffects_Hemizygous_TEMPLATED = subpop_trait_caches.IncorporateEffects_Hemizygous_TEMPLATED; - (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome1, trait_index, subpop_trait_mutationEffect_callbacks); + (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome1, trait, subpop_trait_mutationEffect_callbacks); } else { @@ -6787,7 +6782,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual // diploid, both haplosomes non-null auto IncorporateEffects_Diploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED; - (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait_index, subpop_trait_mutationEffect_callbacks); + (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait, subpop_trait_mutationEffect_callbacks); } } } @@ -6809,6 +6804,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; #if DEBUG_TRAIT_DEMAND std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->symbol_ << "'" << std::endl; @@ -6829,7 +6825,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual std::vector &subpop_trait_mutationEffect_callbacks = subpop_trait_caches.mutationEffect_callbacks_per_trait; auto IncorporateEffects_Haploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED; - (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_trait_mutationEffect_callbacks); + (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait, subpop_trait_mutationEffect_callbacks); } } break; @@ -7039,9 +7035,9 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s // // template < const bool f_additiveTrait, const bool f_callbacks, const bool f_singlecallback> // - void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; - void (Individual::*IncorporateEffects_Hemizygous_TEMPLATED)(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; - void (Individual::*IncorporateEffects_Diploid_TEMPLATED)(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Hemizygous_TEMPLATED)(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Diploid_TEMPLATED)(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks) = nullptr; if (!mutationEffect_callbacks_exist) { @@ -7116,7 +7112,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s if (!haplosome2->IsNull()) { // hemizygous (haplosome2) - (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome2, trait_index, subpop_per_trait_mutationEffect_callbacks); + (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome2, trait, subpop_per_trait_mutationEffect_callbacks); } else { @@ -7126,7 +7122,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s else if (haplosome2->IsNull()) { // hemizygous (haplosome1) - (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome1, trait_index, subpop_per_trait_mutationEffect_callbacks); + (ind->*IncorporateEffects_Hemizygous_TEMPLATED)(species, haplosome1, trait, subpop_per_trait_mutationEffect_callbacks); } else { @@ -7134,7 +7130,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s #if DEBUG_TRAIT_DEMAND //std::cout << " individual #" << individual_index << " existing trait value == " << ind->trait_info_[trait_index].phenotype_ << std::endl; #endif - (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait_index, subpop_per_trait_mutationEffect_callbacks); + (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait, subpop_per_trait_mutationEffect_callbacks); #if DEBUG_TRAIT_DEMAND //std::cout << " individual #" << individual_index << " updated trait value == " << ind->trait_info_[trait_index].phenotype_ << std::endl; #endif @@ -7165,7 +7161,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) continue; - (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait_index, subpop_per_trait_mutationEffect_callbacks); + (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait, subpop_per_trait_mutationEffect_callbacks); } break; } @@ -7213,7 +7209,7 @@ template void Individual_Class::DemandPhenotype_SUBPOP(Species *species, S // for one trait. This will put the result of the calculation into the individual's phenotype information. // This is called by Individual_Class::DemandPhenotype_X(), which loops over chromosomes, traits, and individuals. template -void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) +void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) { #if DEBUG // This method assumes that haplosome is not a null haplosome; the caller needs to guarantee this @@ -7239,6 +7235,7 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos single_callback_mut_type = species->MutationTypeWithID(mutation_type_id); } + slim_trait_index_t trait_index = trait->Index(); MutationBlock *mutation_block = species->SpeciesMutationBlock(); Mutation *mut_block_ptr = mutation_block->mutation_buffer_; const int32_t mutrun_count = haplosome->mutrun_count_; @@ -7270,7 +7267,7 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, trait, effect, p_mutationEffect_callbacks, haplosome->individual_); effect_accumulator += effect; } @@ -7278,7 +7275,7 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, trait, effect, p_mutationEffect_callbacks, haplosome->individual_); if (effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7294,24 +7291,24 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos trait_info_[trait_index].phenotype_ = effect_accumulator; } -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); // Low-level method to calculate a phenotype for one individual, for one diploid chromosome, for one trait. // This will put the result of the calculation into the individual's phenotype information. This is called // by Individual_Class::DemandPhenotype_X(), which loops over chromosomes, traits, and individuals. template -void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) +void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks) { #if DEBUG // This method assumes that haplosome1 and haplosome2 are not null; the caller needs to guarantee this @@ -7337,6 +7334,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos single_callback_mut_type = species->MutationTypeWithID(mutation_type_id); } + slim_trait_index_t trait_index = trait->Index(); MutationBlock *mutation_block = species->SpeciesMutationBlock(); Mutation *mut_block_ptr = mutation_block->mutation_buffer_; const int32_t mutrun_count = haplosome1->mutrun_count_; @@ -7379,7 +7377,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -7387,7 +7385,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7414,7 +7412,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -7422,7 +7420,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7463,7 +7461,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, trait, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += homozygous_effect; } @@ -7471,7 +7469,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, trait, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (homozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7495,7 +7493,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -7503,7 +7501,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7550,7 +7548,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -7558,7 +7556,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7602,7 +7600,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -7610,7 +7608,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7632,7 +7630,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -7640,7 +7638,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7656,12 +7654,12 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos trait_info_[trait_index].phenotype_ = effect_accumulator; } -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); // the rest of the code below is for checking the correctness of the calculations performed by the code above @@ -7807,7 +7805,7 @@ void Individual::_Check_IncorporateEffects_Haploid(Species *species, Haplosome * if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, trait, effect, p_mutationEffect_callbacks, haplosome->individual_); effect_accumulator += effect; } @@ -7815,7 +7813,7 @@ void Individual::_Check_IncorporateEffects_Haploid(Species *species, Haplosome * { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, trait, effect, p_mutationEffect_callbacks, haplosome->individual_); if (effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7879,7 +7877,7 @@ void Individual::_Check_IncorporateEffects_Hemizygous(Species *species, Haplosom if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, trait, effect, p_mutationEffect_callbacks, haplosome->individual_); effect_accumulator += effect; } @@ -7887,7 +7885,7 @@ void Individual::_Check_IncorporateEffects_Hemizygous(Species *species, Haplosom { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, effect, p_mutationEffect_callbacks, haplosome->individual_); + effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, trait, effect, p_mutationEffect_callbacks, haplosome->individual_); if (effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7962,7 +7960,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -7970,7 +7968,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -7997,7 +7995,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -8005,7 +8003,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -8046,7 +8044,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, trait, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += homozygous_effect; } @@ -8054,7 +8052,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, trait, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (homozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -8078,7 +8076,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -8086,7 +8084,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -8133,7 +8131,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -8141,7 +8139,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -8185,7 +8183,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -8193,7 +8191,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; @@ -8215,7 +8213,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_additiveTrait) { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); effect_accumulator += heterozygous_effect; } @@ -8223,7 +8221,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * { if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) { - heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); + heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); if (heterozygous_effect <= (slim_effect_t)0.0) { // not clamped to zero, so we check here trait_info_[trait_index].phenotype_ = 0.0; diff --git a/core/individual.h b/core/individual.h index eac1b853..8b7b2043 100644 --- a/core/individual.h +++ b/core/individual.h @@ -412,10 +412,10 @@ class Individual : public EidosDictionaryUnretained // accumulated into the trait value of the focal individual, which must be set up with an initial value // see also the DemandPhenotype_X() methods in class Individual_Class, which call these methods template - void _IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); + void _IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); template - void _IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks); + void _IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); // Debugging checkback for phenotype calculation; this is very slow, and does not use the non-neutral cache slim_effect_t _CheckPhenotypeForTrait(slim_trait_index_t trait_index); diff --git a/core/mutation.cpp b/core/mutation.cpp index f3683c94..92219b16 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -1525,13 +1525,13 @@ const std::vector *Mutation_Class::Methods(void) const methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_isIndependentDominanceForTrait, kEidosValueMaskLogical))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_isIndependentDominanceForTrait, kEidosValueMaskLogical))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setMutationType, kEidosValueMaskVOID))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index bc42c779..86f40f79 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -1618,12 +1618,12 @@ const std::vector *MutationType_Class::Methods(void) c methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultHemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultHemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N(gStr_trait, gSLiM_Trait_Class)->AddFloat("dominance")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_loggedData, kEidosValueMaskInt | kEidosValueMaskFloat | kEidosValueMaskObject, gEidosDataFrame_Class)) ->AddString_S("kind") ->AddLogical_OS("id", gStaticEidosValue_LogicalF) @@ -1634,7 +1634,7 @@ const std::vector *MutationType_Class::Methods(void) c ->AddLogical_OS("originTick", gStaticEidosValue_LogicalF) ->AddLogical_OS("subpopID", gStaticEidosValue_LogicalF) ->AddLogical_OS("tag", gStaticEidosValue_LogicalF) - ->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL) + ->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL) ->AddLogical_OS("effect", gStaticEidosValue_LogicalF) ->AddLogical_OS("dominance", gStaticEidosValue_LogicalF) ->AddLogical_OS("hemizygousDominance", gStaticEidosValue_LogicalF)); @@ -1650,12 +1650,12 @@ const std::vector *MutationType_Class::Methods(void) c ->AddLogical_OS("originTick", gStaticEidosValue_LogicalF) ->AddLogical_OS("subpopID", gStaticEidosValue_LogicalF) ->AddLogical_OS("tag", gStaticEidosValue_LogicalF) - ->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL) + ->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL) ->AddLogical_OS("effect", gStaticEidosValue_LogicalF) ->AddLogical_OS("dominance", gStaticEidosValue_LogicalF) ->AddLogical_OS("hemizygousDominance", gStaticEidosValue_LogicalF)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddFloat("dominance")); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectDistributionForTrait, kEidosValueMaskVOID))->AddIntStringObject_N("trait", gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N(gStr_trait, gSLiM_Trait_Class)->AddFloat("dominance")); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectDistributionForTrait, kEidosValueMaskVOID))->AddIntStringObject_N(gStr_trait, gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index 4f29a43b..f187990a 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -455,6 +455,35 @@ EidosASTNode *SLiMEidosScript::Parse_SLiMEidosBlock(void) } } + if (current_token_type_ == EidosTokenType::kTokenComma) + { + // A (optional) trait identifier is present, which must be either NULL or a string trait name; add it + Match(EidosTokenType::kTokenComma, "SLiM mutationEffect() callback"); + + if ((current_token_type_ == EidosTokenType::kTokenIdentifier) && (current_token_->token_string_ == "NULL")) + { + callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_)); + + Match(EidosTokenType::kTokenIdentifier, "SLiM mutationEffect() callback"); + } + else if (current_token_type_ == EidosTokenType::kTokenString) + { + callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_)); + + Match(EidosTokenType::kTokenString, "SLiM mutationEffect() callback"); + } + else + { + if (!parse_make_bad_nodes_) + EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; trait identifier expected." << EidosTerminate(current_token_); + + // Make a placeholder bad node, to be error-tolerant + EidosToken *bad_token = new EidosToken(EidosTokenType::kTokenBad, gEidosStr_empty_string, 0, 0, 0, 0, -1); + EidosASTNode *bad_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(bad_token, true); + callback_info_node->AddChild(bad_node); + } + } + Match(EidosTokenType::kTokenRParen, "SLiM mutationEffect() callback"); } else if (current_token_->token_string_.compare(gStr_mutation) == 0) @@ -1111,19 +1140,41 @@ SLiMEidosBlock::SLiMEidosBlock(EidosASTNode *p_root_node) : } else if ((callback_type == EidosTokenType::kTokenIdentifier) && (callback_name.compare(gStr_mutationEffect) == 0)) { - if ((n_callback_children != 1) && (n_callback_children != 2)) - EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SLiMEidosBlock): mutationEffect() callback needs 1 or 2 parameters." << EidosTerminate(callback_token); + if ((n_callback_children != 1) && (n_callback_children != 2) && (n_callback_children != 3)) + EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SLiMEidosBlock): mutationEffect() callback needs 1, 2, or 3 parameters." << EidosTerminate(callback_token); EidosToken *mutation_type_id_token = callback_children[0]->token_; mutation_type_id_ = SLiMEidosScript::ExtractIDFromStringWithPrefix(mutation_type_id_token->token_string_, 'm', mutation_type_id_token); type_ = SLiMEidosBlockType::SLiMEidosMutationEffectCallback; - if (n_callback_children == 2) + if (n_callback_children >= 2) { EidosToken *subpop_id_token = callback_children[1]->token_; - subpopulation_id_ = SLiMEidosScript::ExtractIDFromStringWithPrefix(subpop_id_token->token_string_, 'p', subpop_id_token); + if (subpop_id_token->token_string_ == gEidosStr_NULL) + subpopulation_id_ = -1; // special placeholder that indicates a NULL subpopulation identifier + else + subpopulation_id_ = SLiMEidosScript::ExtractIDFromStringWithPrefix(subpop_id_token->token_string_, 'p', subpop_id_token); + + if (n_callback_children == 3) + { + EidosToken *trait_identifier_token = callback_children[2]->token_; + + if ((trait_identifier_token->token_type_ == EidosTokenType::kTokenIdentifier) && (trait_identifier_token->token_string_ == "NULL")) + { + trait_index_ = -2; + trait_identifier_ = "NULL"; + } + else if (trait_identifier_token->token_type_ == EidosTokenType::kTokenString) + { + trait_index_ = -2; + trait_identifier_ = trait_identifier_token->token_string_; + } + + if (!EidosScript::Eidos_IsIdentifier(trait_identifier_)) + EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SLiMEidosBlock): mutationEffect() trait identifier must be a valid Eidos identifier." << EidosTerminate(callback_token); + } } } else if ((callback_type == EidosTokenType::kTokenIdentifier) && (callback_name.compare(gStr_mutation) == 0)) @@ -1364,6 +1415,7 @@ void SLiMEidosBlock::_ScanNodeForIdentifiersUsed(const EidosASTNode *p_scan_node if (token_string.compare(gStr_self) == 0) contains_self_ = true; if (token_string.compare(gStr_mut) == 0) contains_mut_ = true; + if (token_string.compare(gStr_trait) == 0) contains_trait_ = true; if (token_string.compare(gStr_effect) == 0) contains_effect_ = true; if (token_string.compare(gStr_individual) == 0) contains_individual_ = true; if (token_string.compare(gStr_element) == 0) contains_element_ = true; @@ -1402,6 +1454,7 @@ void SLiMEidosBlock::ScanTreeForIdentifiersUsed(void) { contains_self_ = true; contains_mut_ = true; + contains_trait_ = true; contains_effect_ = true; contains_individual_ = true; contains_element_ = true; @@ -1493,14 +1546,14 @@ void SLiMEidosBlock::PrintDeclaration(std::ostream& p_out, Community *p_communit case SLiMEidosBlockType::SLiMEidosMutationEffectCallback: { - // mutationEffect( [, [, ]]) + // mutationEffect( [, [, ]]) p_out << "mutationEffect(m" << mutation_type_id_; if (subpopulation_id_ != -1) p_out << ", p" << subpopulation_id_; else if (trait_index_ != -1) p_out << ", NULL"; - if (trait_index_ != -1) - p_out << ", " << trait_index_ << ""; + if (trait_identifier_.length()) + p_out << ", '" << trait_identifier_ << "'"; p_out << ")"; break; } diff --git a/core/slim_eidos_block.h b/core/slim_eidos_block.h index 001230ca..030482f9 100644 --- a/core/slim_eidos_block.h +++ b/core/slim_eidos_block.h @@ -158,7 +158,8 @@ class SLiMEidosBlock : public EidosDictionaryUnretained Species *ticks_spec_ = nullptr; // NOT OWNED: the species to which the block is synchronized (only active when that species is active) slim_objectid_t mutation_type_id_ = -1; // -1 if not limited by this slim_objectid_t subpopulation_id_ = -1; // -1 if not limited by this - slim_trait_index_t trait_index_ = -1; // -1 if not limited by this + slim_trait_index_t trait_index_ = -1; // -1 if not limited by this; -2 if it needs to be evaluated from trait_identifier_ below + std::string trait_identifier_; // the original trait identifier string supplied by the user; used to determine trait_index_ slim_objectid_t interaction_type_id_ = -1; // -1 if not limited by this IndividualSex sex_specificity_ = IndividualSex::kUnspecified; // IndividualSex::kUnspecified if not limited by this int64_t chromosome_id_ = -1; // -1 if not limited by this @@ -176,6 +177,7 @@ class SLiMEidosBlock : public EidosDictionaryUnretained bool contains_wildcard_ = false; // "apply", "sapply", "executeLambda", "_executeLambda_OUTER", "ls", "rm"; all other contains_ flags will be T if this is T bool contains_self_ = false; // "self" bool contains_mut_ = false; // "mut" (mutationEffect/mutation callback parameter) + bool contains_trait_ = false; // "trait" (mutationEffect callback parameter) bool contains_effect_ = false; // "effect" (mutationEffect callback parameter) bool contains_individual_ = false; // "individual" (fitnessEffect/mutationEffect/mateChoice/recombination/survival/reproduction callback parameter) bool contains_element_ = false; // "element" (mutation callback parameter) diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 7ef223a7..a7db2b89 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1572,6 +1572,7 @@ const std::string &gStr_isCloning = EidosRegisteredString("isCloning", gID_isClo const std::string &gStr_isSelfing = EidosRegisteredString("isSelfing", gID_isSelfing); const std::string &gStr_parent2 = EidosRegisteredString("parent2", gID_parent2); const std::string &gStr_mut = EidosRegisteredString("mut", gID_mut); +const std::string &gStr_trait = EidosRegisteredString("trait", gID_trait); const std::string &gStr_effect = EidosRegisteredString("effect", gID_effect); const std::string &gStr_homozygous = EidosRegisteredString("homozygous", gID_homozygous); const std::string &gStr_breakpoints = EidosRegisteredString("breakpoints", gID_breakpoints); @@ -1726,6 +1727,7 @@ void SLiM_ConfigureContext(void) gEidosContextReservedSymbols.push_back("receiver"); // defined in ApplyInteractionCallbacks() gEidosContextReservedSymbols.push_back("exerter"); // defined in ApplyInteractionCallbacks() gEidosContextReservedSymbols.push_back("mut"); // defined in ApplyMutationEffectCallbacks() etc. + gEidosContextReservedSymbols.push_back("trait"); // defined in ApplyMutationEffectCallbacks() gEidosContextReservedSymbols.push_back("effect"); // defined in ApplyMutationEffectCallbacks() gEidosContextReservedSymbols.push_back("individual"); // defined in ApplyMutationEffectCallbacks() etc. gEidosContextReservedSymbols.push_back("subpop"); // defined in ApplyMutationEffectCallbacks() etc. diff --git a/core/slim_globals.h b/core/slim_globals.h index 18135187..1c825ffe 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -1142,6 +1142,7 @@ extern const std::string &gStr_isCloning; extern const std::string &gStr_isSelfing; extern const std::string &gStr_parent2; extern const std::string &gStr_mut; +extern const std::string &gStr_trait; extern const std::string &gStr_effect; extern const std::string &gStr_homozygous; extern const std::string &gStr_breakpoints; @@ -1635,6 +1636,7 @@ enum _SLiMGlobalStringID : int { gID_isSelfing, gID_parent2, gID_mut, + gID_trait, gID_effect, gID_homozygous, gID_breakpoints, diff --git a/core/slim_test_core.cpp b/core/slim_test_core.cpp index a05a4a6b..077c330e 100644 --- a/core/slim_test_core.cpp +++ b/core/slim_test_core.cpp @@ -2235,13 +2235,12 @@ void _RunSLiMEidosBlockTests(void) SLiMAssertScriptSuccess(gen1_setup_p1p2p3 + "early() { s1.active = 0; } s1 mutationEffect(m1, p1) { stop(); } 100 early() { ; }", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect() { stop(); } 100 early() { ; }", "mutation type id is required", __LINE__); - SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1, p1, p2) { stop(); } 100 early() { ; }", "unexpected token", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1, m1) { stop(); } 100 early() { ; }", "identifier prefix 'p' was expected", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(p1) { stop(); } 100 early() { ; }", "identifier prefix 'm' was expected", __LINE__); - SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1, NULL) { stop(); } 100 early() { ; }", "identifier prefix 'p' was expected", __LINE__); + SLiMAssertScriptStop(gen1_setup_p1p2p3 + "mutationEffect(m1, NULL) { stop(); } 100 early() { ; }", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { ; } 100 early() { ; }", "return value", __LINE__); - SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { return NULL; } 100 early() { ; }", "return value", __LINE__); + SLiMAssertScriptStop(gen1_setup_p1p2p3 + "mutationEffect(m1) { return NULL; } 100 early() { stop(); }", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { return F; } 100 early() { ; }", "return value", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { return T; } 100 early() { ; }", "return value", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { return 1; } 100 early() { ; }", "return value", __LINE__); @@ -2249,7 +2248,7 @@ void _RunSLiMEidosBlockTests(void) SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { return mut; } 100 early() { ; }", "return value", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { mut; ; } 100 early() { ; }", "return value", __LINE__); - SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { mut; return NULL; } 100 early() { ; }", "return value", __LINE__); + SLiMAssertScriptStop(gen1_setup_p1p2p3 + "mutationEffect(m1) { mut; return NULL; } 100 early() { stop(); }", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { mut; return F; } 100 early() { ; }", "return value", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { mut; return T; } 100 early() { ; }", "return value", __LINE__); SLiMAssertScriptRaise(gen1_setup_p1p2p3 + "mutationEffect(m1) { mut; return 1; } 100 early() { ; }", "return value", __LINE__); diff --git a/core/species.cpp b/core/species.cpp index 503a02d9..9d72b376 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -709,27 +709,57 @@ void Species::AddTrait(Trait *p_trait) trait_from_string_id.emplace(name_string_id, p_trait); } -// This returns the trait index for a single trait, represented by an EidosValue with an integer index or a Trait object +// This returns the trait index for a single trait, represented by an EidosValue with an integer index, a +// string name, a Trait object, or -- only in single-trait models -- NULL to represent the single trait. slim_trait_index_t Species::GetTraitIndexFromEidosValue(EidosValue *trait_value, const std::string &p_method_name) { int64_t trait_index; - if (trait_value->Type() == EidosValueType::kValueInt) + if (trait_value->Type() == EidosValueType::kValueNULL) { - trait_index = trait_value->IntAtIndex_NOCAST(0, nullptr); - } - else - { - const Trait *trait = (const Trait *)trait_value->ObjectElementAtIndex_NOCAST(0, nullptr); + if (TraitCount() != 1) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): " << p_method_name << "() allows NULL to be passed for trait only in single-trait models, since only a single trait may be specified." << EidosTerminate(nullptr); - if (&trait->species_ != this) - EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): " << p_method_name << "() requires trait to belong to the same species as the target mutation type." << EidosTerminate(nullptr); - - trait_index = trait->Index(); + return (slim_trait_index_t)0; } - if ((trait_index < 0) || (trait_index >= TraitCount())) - EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): out-of-range trait index in " << p_method_name << "(); trait index " << trait_index << " is outside the range [0, " << (TraitCount() - 1) << "] for the species." << EidosTerminate(nullptr); + if (trait_value->Count() != 1) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): " << p_method_name << "() requires that only a single trait may be specified." << EidosTerminate(nullptr); + + switch (trait_value->Type()) + { + case EidosValueType::kValueInt: + { + trait_index = trait_value->IntAtIndex_NOCAST(0, nullptr); + + if ((trait_index < 0) || (trait_index >= TraitCount())) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): out-of-range trait index in " << p_method_name << "(); trait index " << trait_index << " is outside the range [0, " << (TraitCount() - 1) << "] for the species." << EidosTerminate(nullptr); + break; + } + case EidosValueType::kValueString: + { + const std::string trait_name = trait_value->StringAtIndex_NOCAST(0, nullptr); + Trait *trait = TraitFromName(trait_name); + + if (trait == nullptr) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): unrecognized trait name in " << p_method_name << "(); trait name " << trait_name << " is not defined for the species." << EidosTerminate(nullptr); + + trait_index = trait->Index(); + break; + } + case EidosValueType::kValueObject: + { + const Trait *trait = (const Trait *)trait_value->ObjectElementAtIndex_NOCAST(0, nullptr); + + if (&trait->species_ != this) + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): " << p_method_name << "() requires trait to belong to the same species as the target mutation type." << EidosTerminate(nullptr); + + trait_index = trait->Index(); + break; + } + default: + EIDOS_TERMINATION << "ERROR (Species::GetTraitIndexFromEidosValue): (internal error) unexpected type for triat_value." << EidosTerminate(nullptr); + } return (slim_trait_index_t)trait_index; } @@ -2792,6 +2822,7 @@ void Species::RunInitializeCallbacks(void) EIDOS_TERMINATION << "ERROR (Species::RunInitializeCallbacks): reproduction() callbacks may not be limited by sex in non-sexual models." << EidosTerminate(script_block->identifier_token_); } { + // validate recombination() callbacks -- particularly their chromosome specifier, which is deferred to here std::vector script_blocks = community_.AllScriptBlocksForSpecies(this); for (auto script_block : script_blocks) @@ -2816,6 +2847,39 @@ void Species::RunInitializeCallbacks(void) } } } + { + // validate mutationEffect() callbacks -- particularly their trait specifier, which is deferred to here + std::vector script_blocks = community_.AllScriptBlocksForSpecies(this); + + for (auto script_block : script_blocks) + { + if ((script_block->type_ == SLiMEidosBlockType::SLiMEidosMutationEffectCallback) && (script_block->trait_index_ == -2)) + { + if (script_block->trait_identifier_.length() == 0) + EIDOS_TERMINATION << "ERROR (Species::RunInitializeCallbacks): (internal error) missing mutationEffect() trait specifier." << EidosTerminate(script_block->identifier_token_); + + Trait *trait; + + if (script_block->trait_identifier_ == "NULL") + { + trait = Traits()[0]; + } + else + { + if (has_implicit_trait_) + EIDOS_TERMINATION << "ERROR (Species::RunInitializeCallbacks): mutationEffect() callbacks may only use a non-NULL trait specifier in models with explicitly declared traits." << EidosTerminate(script_block->identifier_token_); + trait = TraitFromName(script_block->trait_identifier_); + } + + if (!trait) + EIDOS_TERMINATION << "ERROR (Species::RunInitializeCallbacks): mutationEffect() callback declaration references a trait with identifier '" << script_block->trait_identifier_ << "' that has not been declared." << EidosTerminate(script_block->identifier_token_); + + // translate the identifier into an index, which is what the rest of the SLiM core uses + script_block->trait_index_ = trait->Index(); + script_block->trait_identifier_ = trait->Name(); + } + } + } if (nucleotide_based_) { diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index ecba9519..86b4665e 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1674,6 +1674,9 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string if (Eidos_string_hasSuffix(name, "Effect") || Eidos_string_hasSuffix(name, "Dominance") || Eidos_string_hasSuffix(name, "Hemizygous")) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name does not end in 'Effect', 'Dominance', or 'Hemizygous' to avoid naming conflicts and general confusion." << EidosTerminate(); + if (name == "NULL") + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() does not allow a trait name of 'NULL', to avoid naming conflicts and general confusion." << EidosTerminate(); + // type std::string type_string = type_value->StringAtIndex_NOCAST(0, nullptr); TraitType type; @@ -4271,7 +4274,7 @@ EidosValue_SP Species::ExecuteMethod_registerMutationCallback(EidosGlobalStringI return new_script_block->SelfSymbolTableEntry().second; } -// ********************* – (object$)registerMutationEffectCallback(Nis$ id, string$ source, io$ mutType, [Nio$ subpop = NULL], [Ni$ start = NULL], [Ni$ end = NULL]) +// ********************* – (object$)registerMutationEffectCallback(Nis$ id, string$ source, io$ mutType, [Nio$ subpop = NULL], [Ni$ start = NULL], [Ni$ end = NULL], [Niso$ trait = NULL]) // EidosValue_SP Species::ExecuteMethod_registerMutationEffectCallback(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -4290,11 +4293,13 @@ EidosValue_SP Species::ExecuteMethod_registerMutationEffectCallback(EidosGlobalS EidosValue *subpop_value = p_arguments[3].get(); EidosValue *start_value = p_arguments[4].get(); EidosValue *end_value = p_arguments[5].get(); + EidosValue *trait_value = p_arguments[6].get(); - slim_objectid_t script_id = -1; // used if id_value is NULL, to indicate an anonymous block + slim_objectid_t script_id = -1; // used if id_value is NULL, to indicate an anonymous block std::string script_string = source_value->StringAtIndex_NOCAST(0, nullptr); slim_objectid_t mut_type_id = -1; - slim_objectid_t subpop_id = -1; // used if subpop_value is NULL, to indicate applicability to all subpops + slim_objectid_t subpop_id = -1; // used if subpop_value is NULL, to indicate applicability to all subpops + slim_trait_index_t trait_index = -1; // used if trait_value is NULL, to indicate applicability to all traits slim_tick_t start_tick = ((start_value->Type() != EidosValueType::kValueNULL) ? SLiMCastToTickTypeOrRaise(start_value->IntAtIndex_NOCAST(0, nullptr)) : 1); slim_tick_t end_tick = ((end_value->Type() != EidosValueType::kValueNULL) ? SLiMCastToTickTypeOrRaise(end_value->IntAtIndex_NOCAST(0, nullptr)) : SLIM_MAX_TICK + 1); @@ -4309,6 +4314,11 @@ EidosValue_SP Species::ExecuteMethod_registerMutationEffectCallback(EidosGlobalS if (start_tick > end_tick) EIDOS_TERMINATION << "ERROR (Species::ExecuteMethod_registerMutationEffectCallback): registerMutationEffectCallback() requires start <= end." << EidosTerminate(); + // note that unlike other uses of GetTraitIndexFromEidosValue(), we do not want to pass NULL in because it is + // actually allowed; mutationEffect() callbacks can target a single trait or all traits (but not in between) + if (trait_value->Type() != EidosValueType::kValueNULL) + trait_index = GetTraitIndexFromEidosValue(trait_value, "registerMutationEffectCallback"); + community_.CheckScheduling(start_tick, (model_type_ == SLiMModelType::kModelTypeWF) ? SLiMCycleStage::kWFStage6CalculateFitness : SLiMCycleStage::kNonWFStage3CalculateFitness); SLiMEidosBlockType block_type = SLiMEidosBlockType::SLiMEidosMutationEffectCallback; @@ -4316,7 +4326,9 @@ EidosValue_SP Species::ExecuteMethod_registerMutationEffectCallback(EidosGlobalS new_script_block->mutation_type_id_ = mut_type_id; new_script_block->subpopulation_id_ = subpop_id; - new_script_block->trait_index_ = -1; // FIXME MULTITRAIT: should provide the ability to set a trait index + new_script_block->trait_index_ = trait_index; + if (trait_index != -1) + new_script_block->trait_identifier_ = Traits()[trait_index]->Name(); // SPECIES CONSISTENCY CHECK (done by AddScriptBlock()) community_.AddScriptBlock(new_script_block, &p_interpreter, nullptr); // takes ownership from us @@ -4853,7 +4865,7 @@ const std::vector *Species_Class::Methods(void) const methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_chromosomesWithIDs, kEidosValueMaskObject, gSLiM_Chromosome_Class))->AddInt("ids")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_chromosomesWithSymbols, kEidosValueMaskObject, gSLiM_Chromosome_Class))->AddString("symbols")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_countOfMutationsOfType, kEidosValueMaskInt | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_demandPhenotype, kEidosValueMaskVOID))->AddIntObject_N("subpops", gSLiM_Subpopulation_Class)->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_demandPhenotype, kEidosValueMaskVOID))->AddIntObject_N("subpops", gSLiM_Subpopulation_Class)->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddLogical_OS("forceRecalc", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_individualsWithPedigreeIDs, kEidosValueMaskObject, gSLiM_Individual_Class))->AddInt("pedigreeIDs")->AddIntObject_ON("subpops", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_killIndividuals, kEidosValueMaskVOID))->AddObject("individuals", gSLiM_Individual_Class)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_mutationCounts, kEidosValueMaskInt))->AddIntObject_N("subpops", gSLiM_Subpopulation_Class)->AddObject_ON("mutations", gSLiM_Mutation_Class, gStaticEidosValueNULL)); @@ -4870,7 +4882,7 @@ const std::vector *Species_Class::Methods(void) const methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_registerRecombinationCallback, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SLiMEidosBlock_Class))->AddIntString_SN("id")->AddString_S(gEidosStr_source)->AddIntObject_OSN("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddArgWithDefault(kEidosValueMaskNULL | kEidosValueMaskInt | kEidosValueMaskString | kEidosValueMaskObject | kEidosValueMaskOptional | kEidosValueMaskSingleton, "chromosome", gSLiM_Chromosome_Class, gStaticEidosValueNULL)->AddInt_OSN("start", gStaticEidosValueNULL)->AddInt_OSN("end", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_registerSurvivalCallback, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SLiMEidosBlock_Class))->AddIntString_SN("id")->AddString_S(gEidosStr_source)->AddIntObject_OSN("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddInt_OSN("start", gStaticEidosValueNULL)->AddInt_OSN("end", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_registerMutationCallback, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SLiMEidosBlock_Class))->AddIntString_SN("id")->AddString_S(gEidosStr_source)->AddIntObject_OSN("mutType", gSLiM_MutationType_Class, gStaticEidosValueNULL)->AddIntObject_OSN("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddInt_OSN("start", gStaticEidosValueNULL)->AddInt_OSN("end", gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_registerMutationEffectCallback, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SLiMEidosBlock_Class))->AddIntString_SN("id")->AddString_S(gEidosStr_source)->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddIntObject_OSN("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddInt_OSN("start", gStaticEidosValueNULL)->AddInt_OSN("end", gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_registerMutationEffectCallback, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SLiMEidosBlock_Class))->AddIntString_SN("id")->AddString_S(gEidosStr_source)->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddIntObject_OSN("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddInt_OSN("start", gStaticEidosValueNULL)->AddInt_OSN("end", gStaticEidosValueNULL)->AddIntStringObject_OSN(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_registerReproductionCallback, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SLiMEidosBlock_Class))->AddIntString_SN("id")->AddString_S(gEidosStr_source)->AddIntObject_OSN("subpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddString_OSN("sex", gStaticEidosValueNULL)->AddInt_OSN("start", gStaticEidosValueNULL)->AddInt_OSN("end", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_simulationFinished, kEidosValueMaskVOID))); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_skipTick, kEidosValueMaskVOID))); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 9823c5cd..75c4e81e 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -23,6 +23,7 @@ #include "species.h" #include "slim_globals.h" #include "population.h" +#include "trait.h" #include "interaction_type.h" #include "mutation_block.h" #include "eidos_call_signature.h" @@ -1783,10 +1784,33 @@ void Subpopulation::UpdateWFFitnessBuffers(void) } // FIXME MULTITRAIT: should return slim_effect_t so the caller doesn't have to cast -slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual) +slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, Trait *p_trait, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::ApplyMutationEffectCallbacks(): running Eidos callback"); +#if DEBUG + // this should only be called with callbacks that match the situation; the caller is responsible for weeding the list + for (SLiMEidosBlock *callback : p_mutationEffect_callbacks) + { + if (callback->species_spec_ != &species_) + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): (internal error) incorrect mutationEffect() callback species." << EidosTerminate(callback->identifier_token_); + if (callback->type_ != SLiMEidosBlockType::SLiMEidosMutationEffectCallback) + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): (internal error) incorrect mutationEffect() callback type." << EidosTerminate(callback->identifier_token_); + if ((callback->subpopulation_id_ != -1) && (callback->subpopulation_id_ != subpopulation_id_)) + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): (internal error) incorrect mutationEffect() subpopulation id." << EidosTerminate(callback->identifier_token_); + if ((callback->trait_index_ != -1) && (callback->trait_index_ != p_trait->Index())) + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): (internal error) incorrect mutationEffect() trait index." << EidosTerminate(callback->identifier_token_); + + // note that mutation_type_id_ is not expected to match; we check it below + + // It should now be the case that p_fitnessEffect_callbacks only contains active callbacks; the caller should guarantee this. + // This is made possible by the SetInsideTraitOrFitnessCalculation() mechanism, which locks out changes to fitnessEffect() + // and mutationEffect() callbacks inside trait/fitness calculation; the callback set is therefore fixed and can be cached. + if (!callback->block_active_) + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): (internal error) mutationEffect() callback with block_active_ == false included in p_mutationEffect_callbacks." << EidosTerminate(callback->identifier_token_); + } +#endif + #if (SLIMPROFILING == 1) // PROFILING SLIM_PROFILE_BLOCK_START(); @@ -1797,14 +1821,6 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati for (SLiMEidosBlock *mutationEffect_callback : p_mutationEffect_callbacks) { -#if DEBUG - // It should now be the case that p_fitnessEffect_callbacks only contains active callbacks; the caller should guarantee this. - // This is made possible by the SetInsideTraitOrFitnessCalculation() mechanism, which locks out changes to fitnessEffect() - // and mutationEffect() callbacks inside trait/fitness calculation; the callback set is therefore fixed and can be cached. - if (!mutationEffect_callback->block_active_) - EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): (internal error) mutationEffect() callback with block_active_ == false included in p_mutationEffect_callbacks." << EidosTerminate(mutationEffect_callback->identifier_token_); -#endif - if (mutationEffect_callback->block_active_) { slim_objectid_t callback_mutation_type_id = mutationEffect_callback->mutation_type_id_; @@ -1828,6 +1844,8 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati SLIM_ERRSTREAM << EidosDebugPointIndent::Indent() << "#DEBUG mutationEffect(m" << mutationEffect_callback->mutation_type_id_; if (mutationEffect_callback->subpopulation_id_ != -1) SLIM_ERRSTREAM << ", p" << mutationEffect_callback->subpopulation_id_; + if (mutationEffect_callback->trait_index_ != -1) + SLIM_ERRSTREAM << ", '" << mutationEffect_callback->trait_identifier_ << "'"; SLIM_ERRSTREAM << ")"; if (mutationEffect_callback->block_id_ != -1) @@ -1848,17 +1866,14 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati // The script is a constant expression such as "{ return 1.1; }", so we can short-circuit it completely EidosValue_SP result_SP = compound_statement_node->cached_return_value_; EidosValue *result = result_SP.get(); + EidosValueType result_type = result->Type(); - if ((result->Type() != EidosValueType::kValueFloat) || (result->Count() != 1)) - EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): mutationEffect() callbacks must provide a float singleton return value." << EidosTerminate(mutationEffect_callback->identifier_token_); - -#if DEBUG - // this checks the value type at runtime - p_effect = (slim_effect_t)result->FloatData()[0]; -#else - // unsafe cast for speed - p_effect = (slim_effect_t)((EidosValue_Float *)result)->data()[0]; -#endif + if (result_type == EidosValueType::kValueNULL) + p_effect = ((p_trait->Type() == TraitType::kAdditive) ? (slim_effect_t)0.0 : (slim_effect_t)1.0); + else if ((result->Type() == EidosValueType::kValueFloat) && (result->Count() == 1)) + p_effect = (slim_effect_t)((EidosValue_Float *)result)->data()[0]; + else + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): mutationEffect() callbacks must provide a float singleton return value, or NULL to represent neutrality for the focal trait." << EidosTerminate(mutationEffect_callback->identifier_token_); // the cached value is owned by the tree, so we do not dispose of it // there is also no script output to handle @@ -1885,6 +1900,7 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati { // local variables for the callback parameters that we might need to allocate here, and thus need to free below EidosValue_Object local_mut(mut_block_ptr + p_mutation, gSLiM_Mutation_Class); + EidosValue_Object local_trait(p_trait, gSLiM_Trait_Class); EidosValue_Float local_effect((double)p_effect); // We need to actually execute the script; we start a block here to manage the lifetime of the symbol table @@ -1911,6 +1927,11 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati local_mut.StackAllocated(); // prevent Eidos_intrusive_ptr from trying to delete this callback_symbols.InitializeConstantSymbolEntry(gID_mut, EidosValue_SP(&local_mut)); } + if (mutationEffect_callback->contains_trait_) + { + local_trait.StackAllocated(); // prevent Eidos_intrusive_ptr from trying to delete this + callback_symbols.InitializeConstantSymbolEntry(gID_trait, EidosValue_SP(&local_trait)); + } if (mutationEffect_callback->contains_effect_) { local_effect.StackAllocated(); // prevent Eidos_intrusive_ptr from trying to delete this @@ -1934,20 +1955,19 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati try { - // Interpret the script; the result from the interpretation must be a singleton double used as a new fitness value + // Interpret the script; the result from the interpretation must be a singleton float used + // as a new fitness value, or NULL representing a neutral effect for the focal trait; the + // latter is useful for making a mutation neutral across multiple traits EidosValue_SP result_SP = interpreter.EvaluateInternalBlock(mutationEffect_callback->script_); EidosValue *result = result_SP.get(); + EidosValueType result_type = result->Type(); - if ((result->Type() != EidosValueType::kValueFloat) || (result->Count() != 1)) - EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): mutationEffect() callbacks must provide a float singleton return value." << EidosTerminate(mutationEffect_callback->identifier_token_); - -#if DEBUG - // this checks the value type at runtime - p_effect = (slim_effect_t)result->FloatData()[0]; -#else - // unsafe cast for speed - p_effect = (slim_effect_t)((EidosValue_Float *)result)->data()[0]; -#endif + if (result_type == EidosValueType::kValueNULL) + p_effect = ((p_trait->Type() == TraitType::kAdditive) ? (slim_effect_t)0.0 : (slim_effect_t)1.0); + else if ((result->Type() == EidosValueType::kValueFloat) && (result->Count() == 1)) + p_effect = (slim_effect_t)((EidosValue_Float *)result)->data()[0]; + else + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): mutationEffect() callbacks must provide a float singleton return value, or NULL to represent neutrality for the focal trait." << EidosTerminate(mutationEffect_callback->identifier_token_); } catch (...) { @@ -1956,6 +1976,13 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati } } + + // Note that we deliberately do *not* clip the effect to a minimum of 0.0 for multiplicative + // traits here. It is better for the caller to do that, because then they can also short-circuit + // any remaining work. We cannot do that here, so it's better to leave the check for <= 0.0 to + // the caller to perform. We can check for values that are actually illegal here, though. + if (!std::isfinite(p_effect)) + EIDOS_TERMINATION << "ERROR (Subpopulation::ApplyMutationEffectCallbacks): mutationEffect() callbacks cannot return NAN or INF; mutation effects must be finite." << EidosTerminate(mutationEffect_callback->identifier_token_); } } } diff --git a/core/subpopulation.h b/core/subpopulation.h index a3ed1249..8a8068d0 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -165,9 +165,9 @@ class Subpopulation : public EidosDictionaryUnretained // trait. When not in use, that vector should still have one entry per trait, with empty/nullptr values. typedef struct _PerTraitSubpopCaches { std::vector mutationEffect_callbacks_per_trait; // NOT OWNED: mutationEffect() callbacks per subpopulation per trait - void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; - void (Individual::*IncorporateEffects_Hemizygous_TEMPLATED)(Species *species, Haplosome *haplosome, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; - void (Individual::*IncorporateEffects_Diploid_TEMPLATED)(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, slim_trait_index_t trait_index, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Hemizygous_TEMPLATED)(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) = nullptr; + void (Individual::*IncorporateEffects_Diploid_TEMPLATED)(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks) = nullptr; } PerTraitSubpopCaches; std::vector per_trait_subpop_caches_; // one entry per trait, indexed by trait index @@ -394,7 +394,7 @@ class Subpopulation : public EidosDictionaryUnretained void UpdateWFFitnessBuffers(void); // applying mutationEffect() and fitnessEffect() callbacks during trait/fitness calculation - slim_effect_t ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual); + slim_effect_t ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, Trait *p_trait, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual); slim_fitness_t ApplyFitnessEffectCallbacks(std::vector &p_fitnessEffect_callbacks, Individual *p_individual); // generate newly allocated offspring individuals from parent individuals; these methods loop over diff --git a/core/substitution.cpp b/core/substitution.cpp index d7a8dedd..c96e92c8 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -933,10 +933,10 @@ const std::vector *Substitution_Class::Methods(void) c methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_isIndependentDominanceForTrait, kEidosValueMaskLogical))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_isIndependentDominanceForTrait, kEidosValueMaskLogical))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } From 943f61c220fdcad54012541e285bd37d35f0ea9c Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Tue, 13 Jan 2026 13:34:33 -0600 Subject: [PATCH 078/107] loggedData() should always return a DataFrame; other minor fixen --- QtSLiM/help/SLiMHelpClasses.html | 4 +- SLiMgui/SLiMHelpClasses.rtf | 12 ++--- VERSIONS | 2 +- core/mutation_type.cpp | 91 +++++++++++--------------------- core/slim_test_genetics.cpp | 29 ++++++++-- 5 files changed, 65 insertions(+), 73 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index b2803b28..4361db43 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -800,9 +800,9 @@

Returns the parameters that configure the distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DES type "s", and type float for all other DES types.

– (string)effectDistributionTypeForTrait([Niso<Trait> trait = NULL])

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

-

– (ifo<DataFrame>)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

+

– (io<DataFrame>$)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

Returns mutation data produced by the mutation type’s logging facility, as configured by logMutationData().  The data returned can be in the form of means across all logged mutations (for kind="mean"), standard deviations across all logged mutations (for kind="sd"), or separate values for each mutation (for kind="values"); kind="count" is also allowed, and simply returns a singleton integer providing the number of entries (i.e., the number of mutations) that have been recorded.  If logging only of means was enabled (with the meanOnly=T option to logMutationData()), only kind="mean" and kind="count" are allowed, since separate values for each mutation are then not logged.

-

The remaining flags control which columns of data should be returned (for kind options other than "count"); see logMutationData() for a summary of the mutation properties they refer to.  If only one data column is specified by the flags, a vector of values (or a mean or standard deviation) will be returned for that one specified data column.  If more than one flag is set to T, a DataFrame object will be returned with named columns of values (or means, or standard deviations) for each specified data column.  The trait property specifies which traits values should be returned for, with respect to the effect, dominance, and hemizygousDominance flags; see logMutationData() for further description.  Flags set to T for data columns that were not actually logged will simply be ignored; similarly, traits specified by trait that were not actually logged will simply be ignored.  See the Eidos manual for the DataFrame class documentation.

+

The remaining flags control which columns of data should be returned (for kind options other than "count"); see logMutationData() for a summary of the mutation properties they refer to.  A DataFrame object will be returned with named columns of values (or means, or standard deviations) for each specified data column.  The trait property specifies which the traits for which values should be returned, with respect to the effect, dominance, and hemizygousDominance flags; see logMutationData() for further description.  As a convenience, if all of these flags are F (the default), all of the logged data columns will be returned (rather than returning no data at all).  Flags set to T for data columns that were not actually logged will simply be ignored; similarly, traits specified by trait that were not actually logged will simply be ignored.  See the Eidos manual for the DataFrame class documentation.

– (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

Starts or ends logging of data about new mutations belonging to the target mutation type.  If autogeneratedOnly is T (the default), only new mutations generated automatically by SLiM will be logged (including mutations that are substituted in for an auto-generated mutation using a mutation() callback; that is still considered part of the auto-generation process).  If autogeneratedOnly is F, mutations generated in script, such as with addNewMutation(), addNewDrawnMutation(), and reading from files such as VCF, MS, or .trees, will also be logged.  The logged information can be obtained later with the loggedData() method.  Once logging has been started with enable=T it cannot be modified, only stopped with enable=F; and if logging is subsequently resumed with enable=T, any previously logged data will be discarded.  (This can be useful if you wish to limit the size of the in-memory data while continuing to log new data: periodically write the accumulated data to a file and then disable and re-enable logging to discard the old data.)

If meanOnly is F (the default), values for each new mutation will be kept separately.  Beware: the memory usage entailed by this option can be extremely large!  Alternatively, if meanOnly is T, only a running sum, used to compute a mean, will be kept for each type of data; the memory usage for this option will be small and constant, but of course a mean is more useful for some columns of data than others.  If per-mutation data is desired for any one column, use meanOnly=F; this option cannot be controlled independently for the various columns of data being logged.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 3dd6c1d3..4fab3fa1 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -7015,7 +7015,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 class documentation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(ifo)loggedData(string$\'a0kind, [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effect\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ +\f3\fs18 \cf2 \'96\'a0(io$)loggedData(string$\'a0kind, [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effect\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns mutation data produced by the mutation type\'92s logging facility, as configured by @@ -7045,13 +7045,11 @@ The remaining flags control which columns of data should be returned (for \f3\fs18 "count" \f4\fs20 ); see \f3\fs18 logMutationData() -\f4\fs20 for a summary of the mutation properties they refer to. If only one data column is specified by the flags, a vector of values (or a mean or standard deviation) will be returned for that one specified data column. If more than one flag is set to -\f3\fs18 T -\f4\fs20 , a +\f4\fs20 for a summary of the mutation properties they refer to. A \f3\fs18 DataFrame \f4\fs20 object will be returned with named columns of values (or means, or standard deviations) for each specified data column. The \f3\fs18 trait -\f4\fs20 property specifies which traits values should be returned for, with respect to the +\f4\fs20 property specifies which the traits for which values should be returned, with respect to the \f3\fs18 effect \f4\fs20 , \f3\fs18 dominance @@ -7059,7 +7057,9 @@ The remaining flags control which columns of data should be returned (for \f3\fs18 hemizygousDominance \f4\fs20 flags; see \f3\fs18 logMutationData() -\f4\fs20 for further description. Flags set to +\f4\fs20 for further description. As a convenience, if all of these flags are +\f3\fs18 F +\f4\fs20 (the default), all of the logged data columns will be returned (rather than returning no data at all). Flags set to \f3\fs18 T \f4\fs20 for data columns that were not actually logged will simply be ignored; similarly, traits specified by \f3\fs18 trait diff --git a/VERSIONS b/VERSIONS index aa8ce64b..35345a51 100644 --- a/VERSIONS +++ b/VERSIONS @@ -138,7 +138,7 @@ multitrait branch: add a Species demandPhenotype() method, and change the Individual method demandPhenotype() to demandPhenotypeForIndividuals(); the Species method is generally preferred for speed add data logging capabilities to MutationType for easier assessment of requested vs. realized DFEs, etc. - (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], ) - - (ifo)loggedData(string$ kind, ) + - (io)loggedData(string$ kind, ) policy change: mean(integer(0)) or mean(float(0)) now returns NAN rather than NULL, to match R; I just noticed this, and NAN does seem better add optional [Ns$ trait = NULL] specifier to the mutationEffect() callback syntax, allowing mutationEffect() callbacks to be defined as specific to a particular trait note that if this new specifier is absent or NULL, the callback will be called separately for each trait; that preserves backward compatibility diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 86f40f79..d089aed4 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -1027,7 +1027,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID } } -// ********************* - (fo)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], +// ********************* - (io)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], // [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], // [Niso trait = NULL], [l$ effect = F], [l$ dominance = F], [l$ hemizygousDominance = F]) // @@ -1094,8 +1094,6 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho get_trait_indices.push_back(trait_index); } - int trait_count = (int)get_trait_indices.size(); - // all other logical flags bool get_id = id_value->LogicalAtIndex_NOCAST(0, nullptr); bool get_mutationTypeID = mutationTypeID_value->LogicalAtIndex_NOCAST(0, nullptr); @@ -1129,39 +1127,25 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho } // then narrow down to the flags that were actually logged, silently skipping those that weren't - if (get_id && !log_id_) get_id = false; - if (get_mutationTypeID && !log_mutationTypeID_) get_mutationTypeID = false; - if (get_chromosomeID && !log_chromosomeID_) get_chromosomeID = false; - if (get_position && !log_position_) get_position = false; - if (get_nucleotideValue && !log_nucleotideValue_) get_nucleotideValue = false; - if (get_originTick && !log_originTick_) get_originTick = false; - if (get_subpopID && !log_subpopID_) get_subpopID = false; - if (get_tag && !log_tag_) get_tag = false; - if (get_effect && !log_effect_) get_effect = false; - if (get_dominance && !log_dominance_) get_dominance = false; - if (get_hemizygousDominance && !log_hemizygousDominance_) get_hemizygousDominance = false; - - // figure out how many separate items we're actually going to return - int requested_count = (int)get_id + (int)get_mutationTypeID + (int)get_chromosomeID + (int)get_position + - (int)get_nucleotideValue + (int)get_originTick + (int)get_subpopID + (int)get_tag + - ((int)get_effect) * trait_count + ((int)get_dominance) * trait_count + ((int)get_hemizygousDominance) * trait_count; - - if (requested_count == 0) - return gStaticEidosValueNULL; - - EidosDataFrame *dataframe = nullptr; - EidosValue_SP result_SP; - - if (requested_count > 1) - { - // we need to construct a DataFrame object; set it up - dataframe = new EidosDataFrame(); - result_SP = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(dataframe, gEidosDataFrame_Class)); - - // dataframe is now retained by result_SP, so we can release it - dataframe->Release(); - } - + if (!log_id_) get_id = false; + if (!log_mutationTypeID_) get_mutationTypeID = false; + if (!log_chromosomeID_) get_chromosomeID = false; + if (!log_position_) get_position = false; + if (!log_nucleotideValue_) get_nucleotideValue = false; + if (!log_originTick_) get_originTick = false; + if (!log_subpopID_) get_subpopID = false; + if (!log_tag_) get_tag = false; + if (!log_effect_) get_effect = false; + if (!log_dominance_) get_dominance = false; + if (!log_hemizygousDominance_) get_hemizygousDominance = false; + + // we need to construct a DataFrame object; set it up + EidosDataFrame *dataframe = new EidosDataFrame(); + EidosValue_SP result_SP = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(dataframe, gEidosDataFrame_Class)); + + dataframe->Release(); // dataframe is now retained by result_SP, so we can release it + + // now generate columns into dataframe as requested EidosValue *column; if (get_id) @@ -1174,8 +1158,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_id_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys("id", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys("id", EidosValue_SP(column)); } if (get_mutationTypeID) @@ -1188,8 +1171,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_muttype_id_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys("mutationTypeID", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys("mutationTypeID", EidosValue_SP(column)); } if (get_chromosomeID) @@ -1202,8 +1184,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_chromosome_id_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys("chromosomeID", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys("chromosomeID", EidosValue_SP(column)); } if (get_position) @@ -1216,8 +1197,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_position_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys("position", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys("position", EidosValue_SP(column)); } if (get_nucleotideValue) @@ -1230,8 +1210,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_nucleotide_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys("nucleotideValue", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys("nucleotideValue", EidosValue_SP(column)); } if (get_originTick) @@ -1244,8 +1223,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_origin_tick_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys("originTick", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys("originTick", EidosValue_SP(column)); } if (get_subpopID) @@ -1258,8 +1236,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_subpop_id_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys("subpopID", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys("subpopID", EidosValue_SP(column)); } if (get_tag) @@ -1272,8 +1249,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = logged_tag_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys("tag", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys("tag", EidosValue_SP(column)); } for (slim_trait_index_t trait_index : get_trait_indices) @@ -1291,8 +1267,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = (double)trait_log.logged_effect_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys(trait_name + "Effect", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys(trait_name + "Effect", EidosValue_SP(column)); } if (get_dominance) @@ -1305,8 +1280,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = (double)trait_log.logged_dominance_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys(trait_name + "Dominance", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys(trait_name + "Dominance", EidosValue_SP(column)); } if (get_hemizygousDominance) @@ -1319,8 +1293,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = (double)trait_log.logged_hemizygous_dominance_[log_index]; } - if (requested_count == 1) return EidosValue_SP(column); - else dataframe->SetKeyValue_StringKeys(trait_name + "HemizygousDominance", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys(trait_name + "HemizygousDominance", EidosValue_SP(column)); } } @@ -1624,7 +1597,7 @@ const std::vector *MutationType_Class::Methods(void) c methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N(gStr_trait, gSLiM_Trait_Class)->AddFloat("dominance")); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_loggedData, kEidosValueMaskInt | kEidosValueMaskFloat | kEidosValueMaskObject, gEidosDataFrame_Class)) + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_loggedData, kEidosValueMaskInt | kEidosValueMaskObject | kEidosValueMaskSingleton, gEidosDataFrame_Class)) ->AddString_S("kind") ->AddLogical_OS("id", gStaticEidosValue_LogicalF) ->AddLogical_OS("mutationTypeID", gStaticEidosValue_LogicalF) diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index e4c62d24..3098598d 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -158,13 +158,32 @@ void _RunMutationTypeTests(void) initializeGenomicElement(g1, 0, 999999); initializeRecombinationRate(1e-8); } - 1 early() { sim.addSubpop("p1", 50); } + 1 late() { + sim.addSubpop("p1", 50); + if (m1.loggedData(kind="count") != 0) + stop("MutationType data recording initial state incorrect"); + df1 = m1.loggedData(kind="values"); // get all logged columns + if ((df1.nrow != 0) | (df1.ncol != 2)) + stop("MutationType data recording initial state incorrect"); + df2 = m1.loggedData(kind="values", chromosomeID=T); + if ((df2.nrow != 0) | (df2.ncol != 1)) + stop("MutationType data recording initial state incorrect"); + df3 = m1.loggedData(kind="values", chromosomeID=T, position=T, originTick=T); + if ((df3.nrow != 0) | (df3.ncol != 2)) + stop("MutationType data recording initial state incorrect"); + } 100 late() { count = m1.loggedData(kind="count"); - pos = m1.loggedData(kind="values", position=T); - df = m1.loggedData(kind="values", chromosomeID=T, position=T); - - if ((count == 0) | (count != length(pos)) | (count != df.nrow)) + if ((count == 0)) + stop("MutationType data recording recorded no mutations"); + df1 = m1.loggedData(kind="values"); // get all logged columns + if ((df1.nrow != count) | (df1.ncol != 2)) + stop("MutationType data recording problem"); + df2 = m1.loggedData(kind="values", position=T); + if ((df2.nrow != count) | (df2.ncol != 1)) + stop("MutationType data recording problem"); + df3 = m1.loggedData(kind="values", chromosomeID=T, position=T, originTick=T); + if ((df3.nrow != count) | (df3.ncol != 2)) stop("MutationType data recording problem"); } )V0G0N"; From 2b0406d7cf708cf517e52586442fcd475f170da9 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 14 Jan 2026 09:43:51 -0600 Subject: [PATCH 079/107] typo fix --- core/slim_test_genetics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 3098598d..29981816 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -147,7 +147,7 @@ void _RunMutationTypeTests(void) SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); m1.drawEffectForTrait(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 2.0) < 0.1) stop(); }", __LINE__); - // test mutation date recording with logMutationData() and loggedData() + // test mutation data recording with logMutationData() and loggedData() std::string data_recording = R"V0G0N( initialize() { From de18481da88a570b1b00cb59d942aa104e1cbc6d Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 14 Jan 2026 14:42:29 -0600 Subject: [PATCH 080/107] optimize direct-effect traits known to be neutral --- core/mutation.h | 2 +- core/population.cpp | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/core/mutation.h b/core/mutation.h index 133dcde9..78b65bd2 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -115,7 +115,7 @@ class Mutation : public EidosDictionaryRetained // independent_dominance_for_all_traits_ is true if the mutation has been configured to exhibit "independent // dominance" for ALL traits (among those that are non-neutral; traits for which the mutation is neutral are // irrelevant for the determination of this flag). Independent dominance is configured by using NAN as the - // dominance coefficient for for the mutation, for a given trait. This flag is updated if the state of the + // dominance coefficient for the mutation, for a given trait. This flag is updated if the state of the // mutation's dominance changes; it is not sticky. If this flag is set, the mutation can be omitted from // non-neutral caches even if it is non-neutral, because all of its effects can be handled separately by the // independent dominance mechanism. Note that this flag and is_neutral_for_all_traits_ can both be true, diff --git a/core/population.cpp b/core/population.cpp index b71e7f2d..12094a05 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5438,16 +5438,36 @@ void Population::RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recal } } + // move forward to the regime we just chose; UpdateFitness() can consult this to get the current regime + species_.last_nonneutral_regime_ = current_regime; // we need to recalculate phenotypes for traits that have a direct effect on fitness std::vector p_direct_effect_trait_indices; const std::vector &traits = species_.Traits(); for (slim_trait_index_t trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) - if (traits[trait_index]->HasDirectFitnessEffect()) + { + Trait *trait = traits[trait_index]; + + if (trait->HasDirectFitnessEffect()) + { + // Sneaky optimization: we might know that all mutations are completely neutral for this trait; + // if so, we can exclude it from the list of direct-effect traits and skip the demand for its + // phenotypes. This will mean that fitness recalculation does not necessarily demand phenotypes + // for all direct-effect traits, which might be surprising to the user and should be documented. + // See also the optimization for trait_all_mutations_independent_dominance_, which is done inside + // non-neutral cache validation since the effects for such traits go into that cache. + if (trait->trait_all_neutral_mutations_) + { +#if DEBUG_TRAIT_DEMAND + std::cout << "# " << community_.Tick() << " --- RecalculateFitness() removed demand for direct-effect trait '" << trait->Name() << "' because it is known to be neutral" << std::endl; +#endif + + continue; + } + p_direct_effect_trait_indices.push_back(trait_index); - - // move forward to the regime we just chose; UpdateFitness() can consult this to get the current regime - species_.last_nonneutral_regime_ = current_regime; + } + } SLiMEidosBlockType old_executing_block_type = community_.executing_block_type_; community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMutationEffectCallback; // used for both mutationEffect() and fitnessEffect() for simplicity From 2538b8ecabe978a699d2f91d08f970fb2cc03d06 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 16 Jan 2026 23:20:52 -0600 Subject: [PATCH 081/107] re-enable non-neutral caching --- QtSLiM/QtSLiMAppDelegate.cpp | 2 +- ...tSLiMGraphView_PopulationVisualization.cpp | 19 - QtSLiM/QtSLiMWindow.cpp | 2 +- QtSLiM/main.cpp | 4 +- SLiMgui/SLiMWindowController.mm | 2 +- TO_DO | 2 +- VERSIONS | 3 +- core/chromosome.cpp | 5 +- core/chromosome.h | 12 +- core/community.cpp | 13 +- core/haplosome.cpp | 14 +- core/haplosome.h | 10 +- core/individual.cpp | 225 ++++--- core/individual.h | 7 +- core/interaction_type.cpp | 5 +- core/main.cpp | 10 +- core/mutation.cpp | 58 +- core/mutation.h | 2 +- core/mutation_run.cpp | 230 +++---- core/mutation_run.h | 403 +++++++----- core/mutation_type.h | 49 +- core/population.cpp | 233 +------ core/population.h | 2 +- core/slim_globals.cpp | 4 +- core/slim_globals.h | 23 +- core/species.cpp | 603 +++++++++++++++++- core/species.h | 57 +- core/species_eidos.cpp | 19 +- core/subpopulation.cpp | 87 +-- core/subpopulation.h | 4 - core/trait.h | 20 +- eidos/eidos_class_Dictionary.h | 8 +- eidos/eidos_functions_other.cpp | 2 +- eidos/eidos_functions_values.cpp | 18 +- eidos/eidos_globals.cpp | 16 +- eidos/eidos_globals.h | 41 +- eidos/eidos_interpreter.cpp | 39 +- eidos/eidos_interpreter.h | 4 +- eidos/eidos_openmp.h | 4 +- eidos/eidos_test.cpp | 2 +- eidos/eidos_test_functions_math.cpp | 8 +- eidos/eidos_test_functions_statistics.cpp | 2 +- eidos/eidos_test_operators_arithmetic.cpp | 6 +- eidos/eidos_test_operators_other.cpp | 2 +- eidos/eidos_type_table.h | 4 +- 45 files changed, 1427 insertions(+), 858 deletions(-) diff --git a/QtSLiM/QtSLiMAppDelegate.cpp b/QtSLiM/QtSLiMAppDelegate.cpp index a2f5e5ab..f495e53c 100644 --- a/QtSLiM/QtSLiMAppDelegate.cpp +++ b/QtSLiM/QtSLiMAppDelegate.cpp @@ -322,7 +322,7 @@ QtSLiMAppDelegate::~QtSLiMAppDelegate(void) { //qDebug() << "QtSLiMAppDelegate::~QtSLiMAppDelegate"; -#if SLIM_LEAK_CHECKING +#if SLIM_LEAK_CHECKING() QtSLiM_FreeGLBuffers(); #endif diff --git a/QtSLiM/QtSLiMGraphView_PopulationVisualization.cpp b/QtSLiM/QtSLiMGraphView_PopulationVisualization.cpp index 2d444343..d1d14be9 100644 --- a/QtSLiM/QtSLiMGraphView_PopulationVisualization.cpp +++ b/QtSLiM/QtSLiMGraphView_PopulationVisualization.cpp @@ -32,12 +32,6 @@ #include "QtSLiMWindow.h" #include "subpopulation.h" -// This define changes the visualization a little for use making Perry's icon; should be 0 otherwise -#define PERRY_ICON 0 - -#if PERRY_ICON -#warning PERRY_ICON should be 0! -#endif QtSLiMGraphView_PopulationVisualization::QtSLiMGraphView_PopulationVisualization(QWidget *p_parent, QtSLiMWindow *controller) : QtSLiMGraphView(p_parent, controller) { @@ -126,7 +120,6 @@ void QtSLiMGraphView_PopulationVisualization::drawSubpop(QPainter &painter, Subp painter.drawEllipse(center, subpopRadius, subpopRadius); // label it with the subpopulation ID -#if !PERRY_ICON painter.setWorldMatrixEnabled(false); QString popString = QString("p%1").arg(subpopID); @@ -144,7 +137,6 @@ void QtSLiMGraphView_PopulationVisualization::drawSubpop(QPainter &painter, Subp painter.drawText(drawPoint, popString); painter.setWorldMatrixEnabled(true); -#endif } void QtSLiMGraphView_PopulationVisualization::drawArrowFromSubpopToSubpop(QPainter &painter, Subpopulation *sourceSubpop, Subpopulation *destSubpop, double migrantFraction) @@ -182,11 +174,7 @@ void QtSLiMGraphView_PopulationVisualization::drawArrowFromSubpopToSubpop(QPaint // now we figure out our line width, and we calculate a spatial translation of the bezier to shift in slightly off of // the midline, based on the line width, so that incoming and outgoing vectors do not overlap at the start/end points double lineWidth = 0.001 * (sqrt(migrantFraction) / 0.03); // non-linear line width scale -#if PERRY_ICON - double finalShiftMagnitude = 0.0; -#else double finalShiftMagnitude = std::max(lineWidth * 0.75, 0.010); -#endif double finalShiftX = perpendicularFromSourceDX * finalShiftMagnitude / partVecLength; double finalShiftY = perpendicularFromSourceDY * finalShiftMagnitude / partVecLength; double arrowheadSize = std::max(lineWidth * 1.5, 0.008); @@ -208,20 +196,14 @@ void QtSLiMGraphView_PopulationVisualization::drawArrowFromSubpopToSubpop(QPaint double shiftedControl1X = controlPoint1X + finalShiftX, shiftedControl1Y = controlPoint1Y + finalShiftY; double shiftedControl2X = controlPoint2X + finalShiftX, shiftedControl2Y = controlPoint2Y + finalShiftY; -#if PERRY_ICON - bezierLines.moveTo(QPointF(shiftedSourceEndX, shiftedSourceEndY)); - bezierLines.lineTo(QPointF(shiftedDestEndX, shiftedDestEndY)); -#else bezierLines.moveTo(QPointF(shiftedSourceEndX, shiftedSourceEndY)); bezierLines.cubicTo(QPointF(shiftedControl1X, shiftedControl1Y), QPointF(shiftedControl2X, shiftedControl2Y), QPointF(shiftedDestEndX, shiftedDestEndY)); -#endif painter.strokePath(bezierLines, QPen(Qt::black, lineWidth)); // restore the clipping path painter.restore(); -#if !PERRY_ICON // draw the arrowhead; this is oriented along the line from (shiftedDestEndX, shiftedDestEndY) to (shiftedControl2X, shiftedControl2Y), // of length partVecLength, and is calculated using a perpendicular off of that vector QPainterPath bezierArrowheads; @@ -242,7 +224,6 @@ void QtSLiMGraphView_PopulationVisualization::drawArrowFromSubpopToSubpop(QPaint bezierArrowheads.closeSubpath(); painter.fillPath(bezierArrowheads, Qt::black); -#endif } static bool is_line_intersection(double p0_x, double p0_y, double p1_x, double p1_y, double p2_x, double p2_y, double p3_x, double p3_y) diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index 6d5f3a3f..cdf255e1 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -4163,7 +4163,7 @@ void QtSLiMWindow::displayProfileResults(void) } } -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() // // MutationRun metrics, presented per Species // diff --git a/QtSLiM/main.cpp b/QtSLiM/main.cpp index 0c57cc0d..4eb8f72f 100644 --- a/QtSLiM/main.cpp +++ b/QtSLiM/main.cpp @@ -15,7 +15,7 @@ #include "eidos_globals.h" #include "interaction_type.h" -#if SLIM_LEAK_CHECKING +#if SLIM_LEAK_CHECKING() static void clean_up_leak_false_positives(void) { // This does a little cleanup that helps Valgrind to understand that some things have not been leaked. @@ -332,7 +332,7 @@ int main(int argc, char *argv[]) // Run the event loop int appReturn = app.exec(); -#if SLIM_LEAK_CHECKING +#if SLIM_LEAK_CHECKING() clean_up_leak_false_positives(); #endif diff --git a/SLiMgui/SLiMWindowController.mm b/SLiMgui/SLiMWindowController.mm index 2adcf1af..9d02e15d 100644 --- a/SLiMgui/SLiMWindowController.mm +++ b/SLiMgui/SLiMWindowController.mm @@ -1965,7 +1965,7 @@ - (void)displayProfileResults } } -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() // // MutationRun metrics, presented per Species // diff --git a/TO_DO b/TO_DO index ea6659eb..e642d102 100644 --- a/TO_DO +++ b/TO_DO @@ -18,7 +18,7 @@ look for exported symbols that are not tagged with "eidos" or "Eidos" somewhere dump demangled symbols from SLiMgui: nm -g -U -j ./SLiMgui | c++filt -some valgrind commands (set SLIM_LEAK_CHECKING to 1, and change the compile C/C++ flags to -O1 -g): +some valgrind commands (set SLIM_LEAK_CHECKING() to 1, and change the compile C/C++ flags to -O1 -g): valgrind --leak-check=yes --track-origins=yes --expensive-definedness-checks=yes --num-callers=20 ./slim -testEidos (with Debug version) valgrind --leak-check=yes --track-origins=yes --expensive-definedness-checks=yes --num-callers=20 ./slim -testSLiM (with Debug version) valgrind --tool=cachegrind ./slim -seed 1 /Users/bhaller/Desktop/MK_SLiMGUIsim4.txt (with Release version) diff --git a/VERSIONS b/VERSIONS index 35345a51..e85f4ace 100644 --- a/VERSIONS +++ b/VERSIONS @@ -145,6 +145,7 @@ multitrait branch: add trait pseudo-parameter to mutationEffect() callbacks add [Niso$ trait = NULL] parameter to registerMutationEffectCallback() to allow the trait specifier to be given (defaults to NULL for backward compatibility) add a new return option for mutationEffect() callbacks: NULL now indicates neutrality for the focal trait, meaning either 1.0 (multiplicative) or 0.0 (additive) + re-enable non-neutral caching (but without independent dominance optimizations, so far) version 5.1 (Eidos version 4.1): @@ -340,7 +341,7 @@ version 5.0 (Eidos version 4.0): add a "page guide at column" pref to SLiMgui, for those trying to lay out code to a maximum width fix a SLiMgui bug where an error panel would not be visible because it was obscured by a floating window above it, appearing to lock up the whole app fixed a long-standing SLiMgui bug involving mutation frequencies and mutation run reuse when a subset of the subpopulations are selected in the GUI - as part of this, (1) cleaned up and improved the SLIM_CLEAR_HAPLOSOMES debugging code, (2) separated SLiMgui's refcounting more completely from + as part of this, (1) cleaned up and improved the SLIM_CLEAR_HAPLOSOMES() debugging code, (2) separated SLiMgui's refcounting more completely from SLiM's, and (3) fixed unreleased bugs in mutationFrequencies()/mutationCounts()/mutationFrequenciesInHaplosomes()/mutationCountsInHaplosomes() cleaned up the confusion around Dictionary vs. DictionaryBase; methods/functions can now use Dictionary in their signature without problems fix a bug in treeSeqMetadata(); if no metadata was found, it would return object(0), not an empty Dictionary object (and it leaked) diff --git a/core/chromosome.cpp b/core/chromosome.cpp index adfd2c11..abc33fd4 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -1074,10 +1074,7 @@ Mutation *Chromosome::ApplyMutationCallbacks(Mutation *p_mut, Haplosome *p_haplo if ((callback_mutation_type_id == -1) || (callback_mutation_type_id == mutation_type_id)) { -#ifndef DEBUG_POINTS_ENABLED -#error "DEBUG_POINTS_ENABLED is not defined; include eidos_globals.h" -#endif -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; diff --git a/core/chromosome.h b/core/chromosome.h index 67fbbcf8..380034c3 100644 --- a/core/chromosome.h +++ b/core/chromosome.h @@ -283,14 +283,14 @@ class Chromosome : public EidosDictionaryRetained // PROFILING : Chromosome keeps track of some additional profile information that is per-chromosome #if (SLIMPROFILING == 1) -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() std::vector profile_mutcount_history_; // a record of the mutation run count used in each cycle int64_t profile_mutation_total_usage_ = 0; // how many (non-unique) mutations were used by mutation runs, summed across cycles int64_t profile_nonneutral_mutation_total_ = 0; // of profile_mutation_total_usage_, how many were deemed to be nonneutral int64_t profile_mutrun_total_usage_ = 0; // how many (non-unique) mutruns were used by haplosomes, summed across cycles int64_t profile_unique_mutrun_total_ = 0; // of profile_mutrun_total_usage_, how many unique mutruns existed, summed across cycles int64_t profile_mutrun_nonneutral_recache_total_ = 0; // of profile_unique_mutrun_total_, how many mutruns regenerated their nonneutral cache -#endif // SLIM_USE_NONNEUTRAL_CACHES +#endif // SLIM_PROFILE_NONNEUTRAL_CACHES() #endif // (SLIMPROFILING == 1) Chromosome(const Chromosome&) = delete; // no copying @@ -744,13 +744,13 @@ inline __attribute__((always_inline)) Haplosome *Chromosome::NewHaplosome_NONNUL if (mutrun_count_ <= SLIM_HAPLOSOME_MUTRUN_BUFSIZE) { back->mutruns_ = back->run_buffer_; -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() EIDOS_BZERO(back->run_buffer_, SLIM_HAPLOSOME_MUTRUN_BUFSIZE * sizeof(const MutationRun *)); #endif } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() back->mutruns_ = (const MutationRun **)calloc(mutrun_count_, sizeof(const MutationRun *)); #else back->mutruns_ = (const MutationRun **)malloc(mutrun_count_ * sizeof(const MutationRun *)); @@ -759,7 +759,7 @@ inline __attribute__((always_inline)) Haplosome *Chromosome::NewHaplosome_NONNUL } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // the number of mutruns is unchanged, but we need to zero out the reused buffer here EIDOS_BZERO(back->mutruns_, mutrun_count_ * sizeof(const MutationRun *)); #endif @@ -782,7 +782,7 @@ inline __attribute__((always_inline)) void Chromosome::FreeHaplosome(Haplosome * #if DEBUG p_haplosome->individual_ = nullptr; // crash if anybody tries to use this pointer after the free #endif -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() p_haplosome->clear_to_nullptr(); #endif diff --git a/core/community.cpp b/core/community.cpp index f2ddd260..2f2e9c9a 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -52,7 +52,7 @@ #include #include "eidos_globals.h" -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() #include "robin_hood.h" #endif @@ -2421,10 +2421,7 @@ void Community::ExecuteEidosEvent(SLiMEidosBlock *p_script_block) if (!p_script_block->block_active_) return; -#ifndef DEBUG_POINTS_ENABLED -#error "DEBUG_POINTS_ENABLED is not defined; include eidos_globals.h" -#endif -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -2645,7 +2642,7 @@ bool Community::_RunOneTickWF(void) { #if (SLIMPROFILING == 1) // PROFILING -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() if (gEidosProfilingClientCount) for (Species *species : all_species_) species->CollectMutationProfileInfo(); @@ -3008,7 +3005,7 @@ bool Community::_RunOneTickNonWF(void) { #if (SLIMPROFILING == 1) // PROFILING -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() if (gEidosProfilingClientCount) for (Species *species : all_species_) species->CollectMutationProfileInfo(); @@ -3548,7 +3545,7 @@ void Community::StartProfiling(void) signature->body_script_->AST()->ZeroProfileTotals(); } -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() // zero out mutation run metrics that are collected by CollectMutationProfileInfo() for (Species *focal_species : all_species_) { diff --git a/core/haplosome.cpp b/core/haplosome.cpp index f3cb8a88..8209ecc8 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -283,13 +283,13 @@ void Haplosome::ReinitializeHaplosomeToNonNull(Individual *individual, Chromosom if (mutrun_count_ <= SLIM_HAPLOSOME_MUTRUN_BUFSIZE) { mutruns_ = run_buffer_; -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() EIDOS_BZERO(run_buffer_, SLIM_HAPLOSOME_MUTRUN_BUFSIZE * sizeof(const MutationRun *)); #endif } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() mutruns_ = (const MutationRun **)calloc(mutrun_count_, sizeof(const MutationRun *)); #else mutruns_ = (const MutationRun **)malloc(mutrun_count_ * sizeof(const MutationRun *)); @@ -308,13 +308,13 @@ void Haplosome::ReinitializeHaplosomeToNonNull(Individual *individual, Chromosom if (mutrun_count_ <= SLIM_HAPLOSOME_MUTRUN_BUFSIZE) { mutruns_ = run_buffer_; -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() EIDOS_BZERO(run_buffer_, SLIM_HAPLOSOME_MUTRUN_BUFSIZE * sizeof(const MutationRun *)); #endif } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() mutruns_ = (const MutationRun **)calloc(mutrun_count_, sizeof(const MutationRun *)); #else mutruns_ = (const MutationRun **)malloc(mutrun_count_ * sizeof(const MutationRun *)); @@ -323,7 +323,7 @@ void Haplosome::ReinitializeHaplosomeToNonNull(Individual *individual, Chromosom } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // the number of mutruns has not changed; need to zero out EIDOS_BZERO(mutruns_, mutrun_count_ * sizeof(const MutationRun *)); #endif @@ -1495,10 +1495,10 @@ void Haplosome::PrintHaplosomes_MS(std::ostream &p_out, Species &p_species, std: } // make a hash table that looks up the genotype string position from a mutation pointer -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map genotype_string_positions; //typedef robin_hood::pair MAP_PAIR; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map genotype_string_positions; //typedef std::pair MAP_PAIR; #endif diff --git a/core/haplosome.h b/core/haplosome.h index 23f48bad..87b6bba3 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -46,11 +46,11 @@ #include "../treerec/tskit/tables.h" #include "eidos_globals.h" -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() #include "robin_hood.h" typedef robin_hood::unordered_flat_map SLiMBulkOperationHashTable; typedef robin_hood::pair SLiMBulkOperationPair; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() #include typedef std::unordered_map SLiMBulkOperationHashTable; typedef std::pair SLiMBulkOperationPair; @@ -163,13 +163,13 @@ class Haplosome : public EidosObject if (mutrun_count_ <= SLIM_HAPLOSOME_MUTRUN_BUFSIZE) { mutruns_ = run_buffer_; -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() EIDOS_BZERO(run_buffer_, SLIM_HAPLOSOME_MUTRUN_BUFSIZE * sizeof(const MutationRun *)); #endif } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() mutruns_ = (const MutationRun **)calloc(mutrun_count_, sizeof(const MutationRun *)); #else mutruns_ = (const MutationRun **)malloc(mutrun_count_ * sizeof(const MutationRun *)); @@ -325,7 +325,7 @@ class Haplosome : public EidosObject } } -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // BCH 10/15/2024: clearing haplosomes to nullptr is no longer required; it just slows us down. inline __attribute__((always_inline)) void clear_to_nullptr(void) { diff --git a/core/individual.cpp b/core/individual.cpp index 6c7e3126..602181b8 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -98,7 +98,7 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu tagL4_set_ = false; // Initialize x/y/z to 0.0, only when leak-checking (they show up as used before initialized in Valgrind) -#if SLIM_LEAK_CHECKING +#if SLIM_LEAK_CHECKING() spatial_x_ = 0.0; spatial_y_ = 0.0; spatial_z_ = 0.0; @@ -637,7 +637,7 @@ void Individual::PrintIndividuals_SLiM(std::ostream &p_out, const Individual **p if (population.child_generation_valid_) EIDOS_TERMINATION << "ERROR (Individual::PrintIndividuals_SLiM): (internal error) called with child generation active!." << EidosTerminate(); -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() // This method can burn a huge amount of memory and get us killed, if we have a maximum memory usage. It's nice to // try to check for that and terminate with a proper error message, to help the user diagnose the problem. int mem_check_counter = 0, mem_check_mod = 100; @@ -750,7 +750,7 @@ void Individual::PrintIndividuals_SLiM(std::ostream &p_out, const Individual **p p_out << std::endl; -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() if (eidos_do_memory_checks) { mem_check_counter++; @@ -863,7 +863,7 @@ void Individual::PrintIndividuals_SLiM(std::ostream &p_out, const Individual **p p_out << std::endl; -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() if (eidos_do_memory_checks) { mem_check_counter++; @@ -925,7 +925,7 @@ void Individual::PrintIndividuals_SLiM(std::ostream &p_out, const Individual **p AddMutationToPolymorphismMap(&polymorphisms, mut_block_ptr + mut_ptr[mut_index]); } -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() if (eidos_do_memory_checks) { mem_check_counter++; @@ -949,7 +949,7 @@ void Individual::PrintIndividuals_SLiM(std::ostream &p_out, const Individual **p else polymorphism_pair.second.Print_ID(p_out); -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() if (eidos_do_memory_checks) { mem_check_counter++; @@ -1014,7 +1014,7 @@ void Individual::PrintIndividuals_SLiM(std::ostream &p_out, const Individual **p p_out << std::endl; -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() if (eidos_do_memory_checks) { mem_check_counter++; @@ -1054,7 +1054,7 @@ void Individual::PrintIndividuals_SLiM(std::ostream &p_out, const Individual **p else subs[i]->PrintForSLiMOutput(p_out); -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() if (eidos_do_memory_checks) { mem_check_counter++; @@ -6392,21 +6392,23 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotypeForIndividuals(Eido // forceRecalc eidos_logical_t forceRecalc = forceRecalc_value->LogicalAtIndex_NOCAST(0, nullptr); -#if DEBUG_TRAIT_DEMAND - std::cout << "# " << community.Tick() << " --- demandPhenotypeForIndividuals(): for traits {"; +#if DEBUG_TRAIT_DEMAND() + std::cout << "# " << community.Tick() << " ====== demandPhenotypeForIndividuals(): for traits {"; for (slim_trait_index_t trait_index : trait_indices) std::cout << " " << species->Traits()[trait_index]->Name(); std::cout << " } in " << individuals_count << " individuals, forceRecalc == " << (forceRecalc ? "T" : "F") << std::endl; #endif - // validate non-neutral caches and independent-dominance precalculated values - // FIXME MULTITRAIT: VALIDATE NON-NEUTRAL CACHES HERE + // prepare for trait calculations, such as by validating non-neutral caches and independent-dominance precalculated values + std::vector mutationEffect_callbacks = species->CallbackBlocksMatching(community.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); + + species->PrepareForTraitCalculations(mutationEffect_callbacks); // call DemandPhenotype_INDIVIDUALS() to express the demand, across the vector of individuals if (forceRecalc) - DemandPhenotype_INDIVIDUALS(species, individuals_buffer, individuals_count, trait_indices); + DemandPhenotype_INDIVIDUALS(species, individuals_buffer, individuals_count, trait_indices, mutationEffect_callbacks); else - DemandPhenotype_INDIVIDUALS(species, individuals_buffer, individuals_count, trait_indices); + DemandPhenotype_INDIVIDUALS(species, individuals_buffer, individuals_count, trait_indices, mutationEffect_callbacks); // done with trait calculations, unblock species->SetInsideTraitOrFitnessCalculation(false); @@ -6419,9 +6421,66 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotypeForIndividuals(Eido return gStaticEidosValueVOID; } +template +void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) +{ + // Given a vector of trait indices, this scans through them looking for "pure neutral" traits -- traits that + // are known to be neutral even considering the effect of mutationEffect() callbacks, and for which there are + // no non-neutral callbacks that have to be called for their side effects. For any such traits, the effect + // is simply the baseline offset combined with the individual offset. That effect is calculated here, if + // necessary, for each individual, and the pure neutral trait is then removed from the trait indices so it + // takes no further effort downstream. This saves a calculation pass through the mutation list for the trait. + + slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); + + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; + + if (trait->is_pure_neutral_now) + { + TraitType traitType = trait->Type(); + slim_effect_t trait_baseline_offset = trait->BaselineOffset(); + + if (traitType == TraitType::kAdditive) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; + + if (f_force_recalc || std::isnan(trait_info.phenotype_)) + trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + } + } + else // (traitType == TraitType::kMultiplicative) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; + + if (f_force_recalc || std::isnan(trait_info.phenotype_)) + trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + } + } + + // remove the element at index trait_indices_index, decrement the count, and do this index again + trait_indices.erase(trait_indices.begin() + trait_indices_index); + trait_indices_count--; + trait_indices_index--; + +#if DEBUG_TRAIT_DEMAND() + std::cout << "# " << species->community_.Tick() << " --- _HandleAndRemovePureNeutralTraits() resolved demand for trait '" << trait->Name() << "' because it is known to be neutral" << std::endl; +#endif + } + } +} + // This version of DemandPhenotype is called for a vector of individuals. This is called by the Individual method demandPhenotypeForIndividuals(). template -void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) +void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices, std::vector &mutationEffect_callbacks) { // Given a vector of individuals that are all guaranteed to belong to the provided species, and a vector of // trait indices guaranteed to be of length 1 or longer, this method loops over the chromosomes of the @@ -6435,10 +6494,8 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual // First we cache a vector of mutationEffect() callbacks for each subpop; we do this here, rather than at the // start of each tick, so that newly registered callbacks function, and the current active state of each // callback is respected. - std::vector mutationEffect_callbacks = species->CallbackBlocksMatching(species->community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); Population &population = species->population_; bool has_active_callbacks = false; - slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); for (SLiMEidosBlock *callback : mutationEffect_callbacks) { @@ -6474,6 +6531,14 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual } #endif + // If a trait is known to be "pure neutral" -- neutral even including the effects of callbacks, and not + // involving any non-neutral callbacks that need to be called for side effects -- then we want to handle + // the trait up front here and remove it from the vector of trait indices, for efficiency downstream. + // A "pure neutral" trait has phenotypes determined purely by baseline offset and individual offset. + _HandleAndRemovePureNeutralTraits(species, individuals_buffer, individuals_count, trait_indices); + + slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); // do this after _HandleAndRemovePureNeutralTraits()! + // Next we cache method pointers for haploid and diploid chromosomes, which we will use throughout. These // are templated for efficiency, so we have to choose the correct template. That depends on the subpopulation // since each subpopulation might have a different set of mutationEffect() callbacks. Note the template @@ -6611,10 +6676,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual } } - // FIXME MULTITRAIT: We need to recache non-neutral caches for all mutation runs here when running parallel. - // This is maybe also where we would recache the total phenotypic effect of any mutation runs for traits with - // "independent dominance". -#warning re-enable non-neutral caches and recache non-neutral caches first + // Note that our caller has already validated non-neutral caches and independent-dominance effects. // For a given individual, for a given trait, we have to make a decision as to whether we will recalculate or not. That decision gets made // once and then holds across all chromosomes for the individual. But we're looping over chromosomes at the topmost level, so we have a @@ -6699,8 +6761,8 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; -#if DEBUG_TRAIT_DEMAND - std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->symbol_ << "'" << std::endl; +#if DEBUG_TRAIT_DEMAND() + std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->Symbol() << "'" << std::endl; #endif for (int individual_index = 0; individual_index < individuals_count; ++individual_index) @@ -6806,8 +6868,8 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; -#if DEBUG_TRAIT_DEMAND - std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->symbol_ << "'" << std::endl; +#if DEBUG_TRAIT_DEMAND() + std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->Symbol() << "'" << std::endl; #endif for (int individual_index = 0; individual_index < individuals_count; ++individual_index) @@ -6882,16 +6944,15 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual #endif } -template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices); -template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices); +template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *, Individual **, int, std::vector &, std::vector &); +template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *, Individual **, int, std::vector &, std::vector &); // This version of DemandPhenotype is called for a whole subpopulation. This allows for greater efficiency than the individual-level version of this method. // This is called by the Subpopulation method demandPhenotype(), and by SLiM's internal fitness calculation code for traits with a direct effect on fitness. template -void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector p_subpop_mutationEffect_callbacks) +void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector &p_subpop_mutationEffect_callbacks) { -#warning DemandPhenotype_SUBPOP() -- implement me! // FIXME MULTITRAIT: think about shuffling the order in which individuals are handled, in this code path // Given a subpopulation `subpop` that is guaranteed to belong to the provided species, and a vector of @@ -6907,10 +6968,16 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s // callbacks, and does not use the subpopulation per-trait caches used by DemandPhenotype_INDIVIDUALS(). Individual **individuals_buffer = subpop->parent_individuals_.data(); int individuals_count = subpop->parent_subpop_size_; - slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); - // FIXME MULTITRAIT: validate non-neutral caches and independent-dominance effects here -#warning re-enable non-neutral caches and recache non-neutral caches first + // Note that our caller has already validated non-neutral caches and independent-dominance effects. + + // If a trait is known to be "pure neutral" -- neutral even including the effects of callbacks, and not + // involving any non-neutral callbacks that need to be called for side effects -- then we want to handle + // the trait up front here and remove it from the vector of trait indices, for efficiency downstream. + // A "pure neutral" trait has phenotypes determined purely by baseline offset and individual offset. + _HandleAndRemovePureNeutralTraits(species, individuals_buffer, individuals_count, trait_indices); + + slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); // do this after _HandleAndRemovePureNeutralTraits()! // For a given individual, for a given trait, we have to make a decision as to whether we will recalculate or not. That decision gets made // once and then holds across all chromosomes for the individual. But we're looping over chromosomes at the topmost level, so we have a @@ -6934,7 +7001,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s TraitType traitType = trait->Type(); slim_effect_t trait_baseline_offset = trait->BaselineOffset(); -#if DEBUG_TRAIT_DEMAND +#if DEBUG_TRAIT_DEMAND() std::cout << " DemandPhenotype_SUBPOP() trait " << trait->Name() << " (" << traitType << ") has baseline offset " << trait_baseline_offset << std::endl; #endif @@ -6945,7 +7012,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s Individual *ind = individuals_buffer[individual_index]; IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; -#if DEBUG_TRAIT_DEMAND +#if DEBUG_TRAIT_DEMAND() //std::cout << " individual #" << individual_index << " offset " << trait_info.offset_ << std::endl; #endif @@ -6968,7 +7035,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s Individual *ind = individuals_buffer[individual_index]; IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; -#if DEBUG_TRAIT_DEMAND +#if DEBUG_TRAIT_DEMAND() //std::cout << " individual #" << individual_index << " offset " << trait_info.offset_ << std::endl; #endif @@ -7085,8 +7152,8 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s } } -#if DEBUG_TRAIT_DEMAND - std::cout << " DemandPhenotype_SUBPOP() calculating trait " << trait->Name() << " for chromosome '" << chromosome->symbol_ << "'" << std::endl; +#if DEBUG_TRAIT_DEMAND() + std::cout << " DemandPhenotype_SUBPOP() calculating trait " << trait->Name() << " for chromosome '" << chromosome->Symbol() << "'" << std::endl; #endif // Then process the chromosome for the focal trait @@ -7127,11 +7194,11 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s else { // diploid, both haplosomes non-null -#if DEBUG_TRAIT_DEMAND +#if DEBUG_TRAIT_DEMAND() //std::cout << " individual #" << individual_index << " existing trait value == " << ind->trait_info_[trait_index].phenotype_ << std::endl; #endif (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait, subpop_per_trait_mutationEffect_callbacks); -#if DEBUG_TRAIT_DEMAND +#if DEBUG_TRAIT_DEMAND() //std::cout << " individual #" << individual_index << " updated trait value == " << ind->trait_info_[trait_index].phenotype_ << std::endl; #endif } @@ -7195,14 +7262,14 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait // calculations, so I use a larger threshold here of 1e-6; we'll see how this does in practice. if (std::abs(calculated_phenotype - check_phenotype) > (slim_effect_t)1e-6) - EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) phenotype check failed (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_SUBPOP): (internal error) phenotype check failed (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); } } #endif } -template void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector p_mutationEffect_callbacks); -template void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector p_mutationEffect_callbacks); +template void Individual_Class::DemandPhenotype_SUBPOP(Species *, Subpopulation *, std::vector &, std::vector &); +template void Individual_Class::DemandPhenotype_SUBPOP(Species *, Subpopulation *, std::vector &, std::vector &); // Low-level method to calculate a phenotype for one individual, for one haploid (or hemizygous) chromosome, @@ -7219,10 +7286,6 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos // we just need to scan through the haplosome and account for its mutations, using the homozygous mutation // effect (no dominance effects with haploidy), or the hemizygous mutation effect for f_hemizygous == true -//#if SLIM_USE_NONNEUTRAL_CACHES -// int32_t nonneutral_change_counter = species->nonneutral_change_counter_; -// int32_t nonneutral_regime = species->last_nonneutral_regime_; -//#endif // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast MutationType *single_callback_mut_type; @@ -7245,16 +7308,16 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos { const MutationRun *mutrun = haplosome->mutruns_[run_index]; -//#if SLIM_USE_NONNEUTRAL_CACHES -// // Cache non-neutral mutations and read from the non-neutral buffers -// const MutationIndex *haplosome_iter, *haplosome_max; -// -// mutrun->beginend_nonneutral_pointers(mut_block_ptr, &haplosome_iter, &haplosome_max, nonneutral_change_counter, nonneutral_regime); -//#else +#if SLIM_USE_NONNEUTRAL_CACHES() + // Cache non-neutral mutations and read from the non-neutral buffers + const MutationIndex *haplosome_iter, *haplosome_max; + + mutrun->beginend_nonneutral_pointers(&haplosome_iter, &haplosome_max); +#else // Read directly from the MutationRun buffers const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); const MutationIndex *haplosome_max = mutrun->end_pointer_const(); -//#endif +#endif // scan the mutation run and apply mutation effects while (haplosome_iter != haplosome_max) @@ -7291,18 +7354,18 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos trait_info_[trait_index].phenotype_ = effect_accumulator; } -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); // Low-level method to calculate a phenotype for one individual, for one diploid chromosome, for one trait. // This will put the result of the calculation into the individual's phenotype information. This is called @@ -7318,10 +7381,6 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos // both haplosomes are non-null, so we need to scan through and figure out which mutations are // heterozygous and which are homozygous, and assign effects accordingly -//#if SLIM_USE_NONNEUTRAL_CACHES -// int32_t nonneutral_change_counter = species->nonneutral_change_counter_; -// int32_t nonneutral_regime = species->last_nonneutral_regime_; -//#endif // resolve the mutation type for the single callback case; we don't pass this in to keep the non-callback case simple and fast MutationType *single_callback_mut_type; @@ -7345,20 +7404,20 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos const MutationRun *mutrun1 = haplosome1->mutruns_[run_index]; const MutationRun *mutrun2 = haplosome2->mutruns_[run_index]; -//#if SLIM_USE_NONNEUTRAL_CACHES -// // Cache non-neutral mutations and read from the non-neutral buffers -// const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; -// -// mutrun1->beginend_nonneutral_pointers(mut_block_ptr, &haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); -// mutrun2->beginend_nonneutral_pointers(mut_block_ptr, &haplosome2_iter, &haplosome2_max, nonneutral_change_counter, nonneutral_regime); -//#else +#if SLIM_USE_NONNEUTRAL_CACHES() + // Cache non-neutral mutations and read from the non-neutral buffers + const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; + + mutrun1->beginend_nonneutral_pointers(&haplosome1_iter, &haplosome1_max); + mutrun2->beginend_nonneutral_pointers(&haplosome2_iter, &haplosome2_max); +#else // Read directly from the MutationRun buffers const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); const MutationIndex *haplosome2_iter = mutrun2->begin_pointer_const(); const MutationIndex *haplosome1_max = mutrun1->end_pointer_const(); const MutationIndex *haplosome2_max = mutrun2->end_pointer_const(); -//#endif +#endif // first, handle the situation before either haplosome iterator has reached the end of its haplosome, for simplicity/speed if ((haplosome1_iter != haplosome1_max) && (haplosome2_iter != haplosome2_max)) @@ -7654,12 +7713,12 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos trait_info_[trait_index].phenotype_ = effect_accumulator; } -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); -template void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); +template void Individual::_IncorporateEffects_Diploid(Species *, Haplosome *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Diploid(Species *, Haplosome *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Diploid(Species *, Haplosome *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Diploid(Species *, Haplosome *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Diploid(Species *, Haplosome *, Haplosome *, Trait *, std::vector &); +template void Individual::_IncorporateEffects_Diploid(Species *, Haplosome *, Haplosome *, Trait *, std::vector &); // the rest of the code below is for checking the correctness of the calculations performed by the code above diff --git a/core/individual.h b/core/individual.h index 8b7b2043..7156ad39 100644 --- a/core/individual.h +++ b/core/individual.h @@ -452,14 +452,17 @@ class Individual_Class : public EidosDictionaryUnretained_Class EidosValue_SP ExecuteMethod_demandPhenotypeForIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + template + static void _HandleAndRemovePureNeutralTraits(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices); + // phenotype demand for all traits for a vector of target individuals, across all chromosomes // if f_force_recalc is true all values are recalculated; if false, only NAN trait values are recalculated // see also the methods class _IncorporateEffects_X() methods in class Individual, called by this method template - static void DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices); + static void DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices, std::vector &mutationEffect_callbacks); template - static void DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector p_subpop_mutationEffect_callbacks); + static void DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector &p_subpop_mutationEffect_callbacks); }; diff --git a/core/interaction_type.cpp b/core/interaction_type.cpp index 74c0961c..baa870c3 100755 --- a/core/interaction_type.cpp +++ b/core/interaction_type.cpp @@ -1283,10 +1283,7 @@ double InteractionType::ApplyInteractionCallbacks(Individual *p_receiver, Indivi { if (interaction_callback->block_active_) { -#ifndef DEBUG_POINTS_ENABLED -#error "DEBUG_POINTS_ENABLED is not defined; include eidos_globals.h" -#endif -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; diff --git a/core/main.cpp b/core/main.cpp index b3ad7f7a..79697de6 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -161,7 +161,7 @@ static void PrintUsageAndDie(bool p_print_header, bool p_print_full_usage) exit(EXIT_SUCCESS); } -#if SLIM_LEAK_CHECKING +#if SLIM_LEAK_CHECKING() static void clean_up_leak_false_positives(void) { // This does a little cleanup that helps Valgrind to understand that some things have not been leaked. @@ -175,7 +175,7 @@ static void clean_up_leak_false_positives(void) static void test_exit(int test_result) { -#if SLIM_LEAK_CHECKING +#if SLIM_LEAK_CHECKING() clean_up_leak_false_positives(); // sleep() to give time to assess leaks at the command line @@ -688,7 +688,7 @@ int main(int argc, char *argv[]) // FIXME: clang-tidy flags this with bugprone-e if (tree_seq_force && !tree_seq_checks) community->AllSpecies_TSF_Enable(); -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() // We check memory usage at the end of every 10 ticks, to be able to provide the user with a decent error message // if the maximum memory limit is exceeded. Every 10 ticks is a compromise; these checks do take a little time. // Even with a model that runs through ticks very quickly, though, checking every 10 makes little difference. @@ -813,7 +813,7 @@ int main(int argc, char *argv[]) // FIXME: clang-tidy flags this with bugprone-e mem_record[mem_record_index++] = Eidos_GetCurrentRSS() - mem_record_capacity * sizeof(size_t); } -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() if (eidos_do_memory_checks) { mem_check_counter++; @@ -843,7 +843,7 @@ int main(int argc, char *argv[]) // FIXME: clang-tidy flags this with bugprone-e // clean up; but most of this is an unnecessary waste of time in the command-line context Eidos_FlushFiles(); -#if SLIM_LEAK_CHECKING +#if SLIM_LEAK_CHECKING() delete community; community = nullptr; clean_up_leak_false_positives(); diff --git a/core/mutation.cpp b/core/mutation.cpp index 92219b16..184952d9 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -141,7 +141,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ SelfConsistencyCheck(" in Mutation::Mutation()"); #endif -#if DEBUG_MUTATIONS +#if DEBUG_MUTATIONS() std::cout << "Mutation constructed: " << this << std::endl; #endif @@ -283,7 +283,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ SelfConsistencyCheck(" in Mutation::Mutation()"); #endif -#if DEBUG_MUTATIONS +#if DEBUG_MUTATIONS() std::cout << "Mutation constructed: " << this << std::endl; #endif @@ -389,7 +389,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ SelfConsistencyCheck(" in Mutation::Mutation()"); #endif -#if DEBUG_MUTATIONS +#if DEBUG_MUTATIONS() std::cout << "Mutation constructed: " << this << std::endl; #endif @@ -1176,6 +1176,7 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & MutationBlock *mutation_block = species.SpeciesMutationBlock(); MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); + Community &community = species.community_; if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) { @@ -1184,6 +1185,14 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & if (trait) { + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (species.InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): mutation effects may not be changed within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); + if (species.Active() && ((community.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): mutation effects may not be changed during the fitness recalculation tick cycle stage." << EidosTerminate(); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); slim_effect_t new_effect = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); @@ -1204,6 +1213,14 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & if (trait) { + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (species.InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): mutation dominances may not be changed within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); + if (species.Active() && ((community.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): mutation dominances may not be changed during the fitness recalculation tick cycle stage." << EidosTerminate(); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); @@ -1224,6 +1241,14 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & if (trait) { + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (species.InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): mutation dominances may not be changed within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); + if (species.Active() && ((community.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): mutation dominances may not be changed during the fitness recalculation tick cycle stage." << EidosTerminate(); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait->Index(); slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); @@ -1454,6 +1479,15 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *mutType_value = p_arguments[0].get(); Species &species = mutation_type_ptr_->species_; + Community &community = species.community_; + + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (species.InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Mutation::ExecuteMethod_setMutationType): mutation types may not be changed within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); + if (species.Active() && ((community.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Mutation::ExecuteMethod_setMutationType): mutation types may not be changed during the fitness recalculation tick cycle stage." << EidosTerminate(); MutationType *mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(mutType_value, 0, &species.community_, &species, "setMutationType()"); // SPECIES CONSISTENCY CHECK @@ -1576,6 +1610,15 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationBlock *mutation_block = species->SpeciesMutationBlock(); const std::vector &traits = species->Traits(); + Community &community = species->community_; + + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (species->InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): mutation effects may not be changed within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); + if (species->Active() && ((community.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): mutation effects may not be changed during the fitness recalculation tick cycle stage." << EidosTerminate(); // get the trait indices, with bounds-checking std::vector trait_indices; @@ -1770,6 +1813,15 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri MutationBlock *mutation_block = species->SpeciesMutationBlock(); const std::vector &traits = species->Traits(); + Community &community = species->community_; + + // TIMING RESTRICTION + // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made + // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu + if (species->InsideTraitOrFitnessCalculation()) + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_" << method_name << "): mutation dominances may not be changed within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); + if (species->Active() && ((community.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_" << method_name << "): mutation dominances may not be changed during the fitness recalculation tick cycle stage." << EidosTerminate(); // get the trait indices, with bounds-checking std::vector trait_indices; diff --git a/core/mutation.h b/core/mutation.h index 78b65bd2..876714f8 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -161,7 +161,7 @@ class Mutation : public EidosDictionaryRetained Mutation(slim_mutationid_t p_mutation_id, MutationType *p_mutation_type_ptr, slim_chromosome_index_t p_chromosome_index, slim_position_t p_position, slim_effect_t p_selection_coeff, slim_effect_t p_dominance_coeff, slim_objectid_t p_subpop_index, slim_tick_t p_tick, int8_t p_nucleotide); // a destructor is needed now that we inherit from EidosDictionaryRetained; we want it to be as minimal as possible, though, and inline -#if DEBUG_MUTATIONS +#if DEBUG_MUTATIONS() inline virtual ~Mutation(void) override { std::cout << "Mutation destructed: " << this << std::endl; diff --git a/core/mutation_run.cpp b/core/mutation_run.cpp index 76a39402..743668b7 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -29,6 +29,21 @@ int64_t MutationRun::sOperationID = 0; +std::ostream& operator<<(std::ostream& p_out, TraitCalculationRegime p_trait_type) +{ + switch (p_trait_type) + { + case TraitCalculationRegime::kUndefined: p_out << "kUndefined"; break; + case TraitCalculationRegime::kPureNeutral: p_out << "kPureNeutral"; break; + case TraitCalculationRegime::kNoActiveCallbacks: p_out << "kNoActiveCallbacks"; break; + case TraitCalculationRegime::kAllNeutralCallbacks: p_out << "kAllNeutralCallbacks"; break; + case TraitCalculationRegime::kNonNeutralCallbacks: p_out << "kNonNeutralCallbacks"; break; + } + + return p_out; +} + + MutationRun::MutationRun(void) #ifdef DEBUG_LOCKS_ENABLED : mutrun_use_count_LOCK("mutrun_use_count_LOCK") @@ -45,7 +60,7 @@ MutationRun::~MutationRun(void) { free(mutations_); -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_USE_NONNEUTRAL_CACHES() if (nonneutral_mutations_) free(nonneutral_mutations_); #endif @@ -341,7 +356,7 @@ void MutationRun::_RemoveFixedMutations(Mutation *p_mut_block_ptr) { mutation_count_ -= (haplosome_iter - haplosome_backfill_iter); -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_USE_NONNEUTRAL_CACHES() // invalidate the nonneutral mutation cache nonneutral_mutations_count_ = -1; #endif @@ -442,116 +457,105 @@ void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_hal } -#if SLIM_USE_NONNEUTRAL_CACHES - -//void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const -//{ -// // -// // Regime 1 means there are no mutationEffect() callbacks at all, so neutrality can be assessed -// // simply by looking at selection_coeff_ != 0.0. The mutation type is irrelevant. -// // -// zero_out_nonneutral_buffer(); -// -// // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed -// for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) -// { -// MutationIndex mutindex = mutations_[bufindex]; -// Mutation *mutptr = p_mut_block_ptr + mutindex; -// -// if (!mutptr->is_neutral_for_all_traits_) -// add_to_nonneutral_buffer(mutindex); -// } -//} -// -//void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const -//{ -// // FIXME MULTICHROM: I think regime 2 needs to be rethought with multitrait. We won't have -// // constant mutationEffect() callbacks any more; all of those optimizations, including regime 2, -// // can be ripped out. Instead, QTL mutations will contribute an additive effect to a quantitative -// // trait, and their effect on whatever multiplicative trait might be in the model will be zero -// // (absent pleiotropy). That is the case that we will now want to detect and optimize somehow. -// // I'm not sure what the right strategy would be. What exactly will the role of non-neutral -// // caches be? Should mutations that are non-neutral for *any* trait be put into them, which would -// // be best for universal pleiotropy? Or maybe we have separate non-neutral caches for each trait, -// // which would be best for zero pleiotropy? Or some kind of adaptive approach? -// -// // -// // Regime 2 means the only mutationEffect() callbacks are (a) constant-effect, (b) neutral (i.e., -// // make their mutation type become neutral), and (c) global (i.e. apply to all subpopulations). -// // Here neutrality is assessed by first consulting the set_neutral_by_global_active_callback -// // flag of MutationType, which is set up by RecalculateFitness() for us. If that is true, -// // the mutation is neutral; if false, selection_coeff_ is reliable. Note the code below uses -// // the exact way that the C operator && works to implement this order of checks. -// // -// zero_out_nonneutral_buffer(); -// -// // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed -// for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) -// { -// MutationIndex mutindex = mutations_[bufindex]; -// Mutation *mutptr = p_mut_block_ptr + mutindex; -// -// // The result of && is not order-dependent, but the first condition is checked first. -// // I expect many mutations would fail the first test (thus short-circuiting), whereas -// // few would fail the second test (i.e. actually be 0.0) in a QTL model. -// if ((!mutptr->mutation_type_ptr_->set_neutral_by_global_active_callback_) && !mutptr->is_neutral_for_all_traits_) -// add_to_nonneutral_buffer(mutindex); -// } -//} -// -//void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const -//{ -// // -// // Regime 3 means that there are mutationEffect() callbacks beyond the constant neutral global -// // callbacks of regime 2, so if a mutation's muttype is subject to any mutationEffect() callbacks -// // at all, whether active or not, that mutation must be considered to be non-neutral (because -// // a rogue callback could enable/disable other callbacks). This is determined by consulting -// // the subject_to_mutationEffect_callback flag of MutationType, set up by RecalculateFitness() -// // for us. If that flag is not set, then the selection_coeff_ is reliable as usual. -// // -// zero_out_nonneutral_buffer(); -// -// // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed -// for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) -// { -// MutationIndex mutindex = mutations_[bufindex]; -// Mutation *mutptr = p_mut_block_ptr + mutindex; -// -// // The result of || is not order-dependent, but the first condition is checked first. -// // I have reordered this to put the fast test first; or I'm guessing it's the fast test. -// if (!mutptr->is_neutral_for_all_traits_ || (mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_)) -// add_to_nonneutral_buffer(mutindex); -// } -//} -// -//void MutationRun::check_nonneutral_mutation_cache() const -//{ -// if (!nonneutral_mutations_) -// EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache not allocated." << EidosTerminate(); -// if (nonneutral_mutations_count_ == -1) -// EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) unvalidated cache." << EidosTerminate(); -// if (nonneutral_mutations_count_ > nonneutral_mutation_capacity_) -// EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache size exceeds cache capacity." << EidosTerminate(); -// -// // Check for correctness in regime 1. Now that we have three regimes, this isn't really worth maintaining; -// // it really just replicates the above logic exactly, so it is not a very effective cross-check. -// -// /* -// int32_t cache_index = 0; -// -// for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) -// { -// MutationIndex mutindex = mutations_[bufindex]; -// Mutation *mutptr = gSLiM_Mutation_Block + mutindex; -// -// if (mutptr->selection_coeff_ != 0.0) -// if (*(nonneutral_mutations_ + cache_index++) != mutindex) -// EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache_REGIME_1): (internal error) unsynchronized cache." << EidosTerminate(); -// } -// */ -//} +#if SLIM_USE_NONNEUTRAL_CACHES() -#endif +void MutationRun::cache_nonneutral_mutations_REGIME_0(void) const +{ + // + // Regime 0 means there are no genetic effects at all, so we can simply empty the non-neutral cache. + // + zero_out_nonneutral_buffer(); +} + +void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const +{ + // + // Regime 1 means there are no active mutationEffect() callbacks at all, so neutrality can be assessed + // simply by looking at whether the mutation itself is neutral. The mutation type is irrelevant. + // + zero_out_nonneutral_buffer(); + + // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed + for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) + { + MutationIndex mutindex = mutations_[bufindex]; + Mutation *mutptr = p_mut_block_ptr + mutindex; + + if (!mutptr->is_neutral_for_all_traits_) + add_to_nonneutral_buffer(mutindex); + } +} + +void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const +{ + // + // Regime 2 means the only mutationEffect() callbacks are (a) constant-effect, (b) neutral (i.e., + // make their mutation type become neutral), and (c) global (i.e. apply to all subpopulations). + // Here neutrality is assessed by first consulting the subject_to_mutationEffect_callback_ flag + // of MutationType; if it is set, the mutation is neutral because the callback is known to be + // global-neutral. Otherwise, the mutation's neutral flag is reliable. + // + zero_out_nonneutral_buffer(); + + // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed + for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) + { + MutationIndex mutindex = mutations_[bufindex]; + Mutation *mutptr = p_mut_block_ptr + mutindex; + + // The result of && is not order-dependent, but the first condition is checked first. + // I expect many mutations would fail the first test (thus short-circuiting), whereas + // few would fail the second test (i.e. actually be neutral) in a model in this regime. + if ((!mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_) && !mutptr->is_neutral_for_all_traits_) + add_to_nonneutral_buffer(mutindex); + } +} + +void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const +{ + // + // Regime 3 means that there are active mutationEffect() callbacks beyond the constant neutral global + // callbacks of regime 2 -- but those non-global-neutral callbacks might not affect the mutation. So + // we first consult subject_to_mutationEffect_callback_ to find out if a callback of any kind affects + // the mutation. If that is true, then we consult subject_to_non_neutral_callback_; if that is true, + // the mutation is affected by a nonneutral callback, which must be called even if it is overridden + // by a global-neutral callback since it might have side effects. If subject_to_non_neutral_callback_ + // is false, we know the mutation is rendered neutral by a global-neutral callback. And if the test + // of subject_to_mutationEffect_callback_ was false, the mutation's neutral flag is reliable. + // + zero_out_nonneutral_buffer(); + + // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed + for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) + { + MutationIndex mutindex = mutations_[bufindex]; + Mutation *mutptr = p_mut_block_ptr + mutindex; + MutationType *muttypeptr = mutptr->mutation_type_ptr_; + + if (muttypeptr->subject_to_mutationEffect_callback_) + { + if (muttypeptr->subject_to_non_neutral_callback_) + add_to_nonneutral_buffer(mutindex); + } + else + { + if (!mutptr->is_neutral_for_all_traits_) + add_to_nonneutral_buffer(mutindex); + } + } +} + +void MutationRun::check_nonneutral_mutation_cache() const +{ + if (!nonneutral_mutations_) + EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache not allocated." << EidosTerminate(); + if (nonneutral_mutations_count_ == -1) + EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) unvalidated cache." << EidosTerminate(); + if (nonneutral_mutations_count_ > nonneutral_mutation_capacity_) + EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache size exceeds cache capacity." << EidosTerminate(); +} + +#endif // SLIM_USE_NONNEUTRAL_CACHES() // Shorthand for clear(), then copy_from_run(p_mutations_to_set), then insert_sorted_mutation() on every // mutation in p_mutations_to_add, with checks with enforce_stack_policy_for_addition(). The point of @@ -664,7 +668,11 @@ size_t MutationRun::MemoryUsageForMutationIndexBuffers(void) const size_t MutationRun::MemoryUsageForNonneutralCaches(void) const { +#if SLIM_USE_NONNEUTRAL_CACHES() return nonneutral_mutation_capacity_ * sizeof(MutationIndex); +#else + return 0; +#endif } diff --git a/core/mutation_run.h b/core/mutation_run.h index bcca5816..5234e178 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -92,7 +92,33 @@ typedef struct MutationRunContext { // fitness calculations, but does consume additional memory, and is not always advantageous. Define to 0 to disable this feature. // I'm not sure how long I will maintain the ability to disable these caches; the overhead is quite small, so I think it would be OK // to just make this always be on. At present this flag is mostly useful for testing purposes. -#define SLIM_USE_NONNEUTRAL_CACHES 1 +// +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define SLIM_USE_NONNEUTRAL_CACHES() 1 + +#warning FIXME MULTITRAIT turn on profiling of non-neutral caches eventually +#if SLIM_USE_NONNEUTRAL_CACHES() && (SLIMPROFILING == 1) +// PROFILING: this flag should be 1 to profile the use of non-neutral caches. It's a separate flag to +// separate out the profiling ramifications of non-neutral caches from all the rest of that code. +// +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define SLIM_PROFILE_NONNEUTRAL_CACHES() 0 // FIXME MULTITRAIT: should be 1 +#else +#define SLIM_PROFILE_NONNEUTRAL_CACHES() 0 +#endif // SLIM_USE_NONNEUTRAL_CACHES() && (SLIMPROFILING == 1) + +// This enumeration represents a particular modality for the calculation of trait values, depending upon the +// callbacks present, whether independent dominance is being used, and other factors. This affects the way +// that non-neutral caches for MutationRun are constructed, and can be used in other ways as well. +enum class TraitCalculationRegime : int8_t { + kUndefined = -1, // used for the initial state + kPureNeutral = 0, // all mutations are effectively neutral; genetics can be skipped + kNoActiveCallbacks, // no active callbacks, so you don't have to look at the mutation type at all + kAllNeutralCallbacks, // we can skip actually calling all callbacks, since they are all global-neutral + kNonNeutralCallbacks, // we can't skip calling all callbacks, since some are non-global-neutral +}; + +std::ostream& operator<<(std::ostream& p_out, TraitCalculationRegime p_trait_type); class MutationRun @@ -121,73 +147,155 @@ class MutationRun mutable EidosDebugLock mutrun_use_count_LOCK; #endif -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_USE_NONNEUTRAL_CACHES() - // Non-neutral mutation caching. This is a somewhat complex scheme designed to speed up fitness calculations. - // The idea is that the mutation run can cache, once, a list of all of the non-neutral mutations it contains, - // and then the fitness code can refer to that cached list from then on, saving a huge amount of looping over - // neutral mutations in many simulations. This simple idea is complicated by a few factors. First of all, if - // the mutation run changes, the cache needs to be invalidated. Second, if the external information that the - // cache relies upon changes, the cache needs to be invalidated. That external information consists of (a) the - // selection coefficients of mutations, and (b) the existence and state of mutationEffect() callbacks. There - // are three separate regimes in which these caches are used: // - // 1. No mutationEffect() callbacks defined. Here caches depend solely upon mutation selection coefficients, - // and can be carried forward through cycles with impunity. If any mutation's effect is changed between - // zero and non-zero, a global flag in Species (nonneutral_change_counter_) marks all caches as invalid. + // Non-neutral mutation caching + // + // This is a complex area, designed to optimize SLiM's performance across a wide variety of models when it + // calculates the genetic component of the trait values for individuals from the mutations present in those + // individuals. Each mutation run can keep a "non-neutral cache" that is composed of multiple elements: + // + // - a "non-neutral mutation buffer" that contains references to a set of mutations that need to be included + // in trait value calculations *with* knowledge of whether the mutations are homozygous or heterozygous + // + // - a set of "independent-dominance effect caches", one per trait; these are slim_effect_t values that + // represent the composite effects of all mutations that satisfy certain "independent dominance" + // criteria and can thus be handled *without* knowledge of homozygosity vs. heterozygosity + // + // Any given mutation will be treated in one of three ways: it will be placed in the non-neutral mutation + // buffer, it will be included in the independent-dominance effect caches, or (if it is deemed completely + // neutral) it will not be included in either since it can be ignored for purposes of trait value calculation. + // This determination is managed primarily based upon the mutation type of the mutation, because that is + // the level at which mutationEffect() callbacks operate -- they make particular mutation types become one + // of three things: (a) completely neutral, (b) non-neutral but with trait effects that are either neutral or + // independent-dominance, or (c) non-neutral with at least one trait effect that is non-neutral and non- + // independent-dominance. Category (a) is left out of the non-neutral caching entirely; category (b) is put + // into the independent-dominance effect caches; and category (c) is put into the non-neutral mutation buffer. + // Whenever the non-neutral mutation caches are about to be used -- whenever trait values are about to be + // re-calculated -- the status of all mutation types is re-checked, with respect to their intrinsic status + // due to the mutations they represent, and also how that status is modified by all of the currently active + // mutationEffect() callbacks. If the status of all of the mutation types is unchanged from the last cache + // validation, the existing cache state can be used; otherwise, the existing caches have to be thrown out and + // remade from scratch, because a mutation type's change in status means that a whole set of mutations needs + // to change how it is treated in the caches, which cannot be done incrementally. + // + // The story is a little more complicated than that, because we can be a bit smarter. If a mutationEffect() + // callback is present for a mutation type, that puts mutations of that mutation type firmly into category (a), + // (b), or (c), or it means that we simply don't know what the callback will do and so we're in category (c) + // due to our lack of knowledge. But there is also the case where there are no mutationEffect() callbacks for + // a given mutation type! In this case -- probably the most common case, actually -- we only care about the + // state of the mutation itself; its mutation type is irrelevant since it exerts no control. Here we use + // precalculated flags on Mutation to guide how the mutation is handled for caching. If the mutation is + // neutral for all traits, it goes into category (a). If it is non-neutral but its non-neutral effects are + // all independent-dominance, it is category (b). If it is non-neutral with non-independent effects, it is + // category (c). // - // 2. Only constant-effect neutral callbacks are defined: "return 0.0;". RecalculateFitness() runs through - // mutation types and callbacks, and figures this state out and sets a flag in each mutation type as to - // whether it is effectively neutral, after considering these constant-effect callbacks, or not. This - // state changes in every cycle, so caches cannot be carried forward from cycle to cycle - // in this regime unless the state of the callbacks, with respect to making mutation types neutral, is - // unchanged. If RecalculateFitness() detects a callback change, it sets the global all-invalid flag. + // The trickiest thing is if constant-effect mutationEffect() callbacks are present that would allow an + // inference to be drawn for a particular mutation. Suppose a mutation is neutral for all traits but B; for + // B it is non-neutral. Some other mutations for the same mutation type are also non-neutral for trait A, + // so the mutation type itself is known to be non-neutral for both A and B. Then a mutationEffect() callback + // makes the mutation type neutral for B. The mutation type is still known to be non-neutral, since it has + // mutations that are non-neutral for A. But our particular focal mutation has been rendered completely + // neutral by the callback, and could be omitted from the non-neutral cache. I don't see a reasonable way to + // make this kind of determination, however. The present design relies on just two basic inferences: (a) do + // we know, simply from the mutation type itself, the category a mutation goes into? And (b) if the mutation + // type is not relevant (no callbacks for it), then what category does the mutation's own state say that it + // should be in? Inferences that combine these two levels are too complex, and are not attempted. // - // 3. At least one non-constant callback is defined. RecalculateFitness() figures this out, and if this is - // the case, the non-neutral cache must include all mutations for which their muttype has a callback - // defined at all, whether constant or not, neutral or not, active or not, because the callback regime - // itself could change unpredictably. These caches cannot be carried forward unless the state of the - // callbacks, with respect to which mutation types are influenced by them, is unchanged. If a callback - // change is detected by RecalculateFitness(), it sets the global all-invalid flag. + // There are a few smaller details here. The non-neutral cache for one mutation run can be invalidated on + // its own, if that mutation run is modified (by adding or removing a mutation). Each mutation run thus has + // its own "I'm valid" flag, and all mutation runs get checked and re-validated before non-neutral caches are + // used. Second, a particular position in a particular chromosome can be invalidated; this occurs when an + // existing mutation changes its effect between neutral and non-neutral, or its dominance between independent + // and non-independent, for example. When this occurs, we want to invalidate all mutation runs that represent + // that position (because they might be affected by the change), without invalidating the rest of the caches; + // for this purpose, each Chromosome keeps a per-mutrun-slot "I'm valid" flag, and if one of those flags is + // set to invalid, all mutation runs in the affected slot get re-validated before non-neutral caches are used. + // (FIXME MULTITRAIT: For right now, we will just have a per-Chromosome flag, which can be refined later; + // leveraging the specific mutrun slot is a little complicated because of mutrun experiments.) // - // (FIXME) One could imagine inserting a regime between 2 and 3 that would allow a mix of constant and - // non-constant callbacks, as long as the non-constant callbacks were "well-behaved" – no use of the - // active property, no executeLambda, etc. – so that SLiM could know that the constant callbacks would - // apply if they were active. This could be pretty useful for models that have a mix of QTLs (using - // a constant neutral callbacks) and other loci that are governed by mutationEffect() callbacks. This - // strikes me as an edge case, though; mostly models are either QTL models or non-QTL models, I think. + // There are some special cases. If all mutations in the model are neutral, the non-neutral caches are not + // used at all, and trait calculation can skip looking at genetics completely. If all mutations in the model + // are non-neutral (as is common in tree-seq models), then (a) if all are independent-dominance only the + // independent-dominance effect caches get set up, (b) if none are independent-dominance then the non-neutral + // caches do not get set up at all and trait calculations use the main mutation run buffers instead (no point + // in copying every mutation into an identical buffer of non-neutral mutations), or (c) if there is a mix + // of independent and non-independent-dominance, the non-neutral cache is set up as usual, since it remains + // worthwhile to divide mutations into those two categories. // - // When models switch between one regime and another, they generally need to recache, since the criteria - // for inclusion in the cache differs from regime to regime. This is handled by RecalculateFitness(). - // The last regime used (for the previous cycle) is remembered in sim.last_nonneutral_regime_. + // There is also the special case of the ploidy of particular chromosomes. The description above applies to + // mutation runs that represent diploid chromosomes. For haploid chromosomes, effects are "independent" by + // definition; ploidy is not a factor. For such mutation runs, the non-neutral mutation buffers are not + // used at all; instead, all non-neutral mutations are included in the independent-dominance effect caches, + // and all trait calculations can be based simply upon those caches (making haploid models very fast). For + // intrinsically diploid chromosomes that can be present in one copy in a given individual (like an X), the + // caching is currently done following the standard diploid approach, but with the addition of per-trait + // hemizygous effect caches kept in each mutation run as well. This adds substantial complication, but + // seems worth it given that typically 50% of individuals for a chromosome like an X would be hemizygous, + // and some models might be models of only an X; that's a lot of performance to leave on the table. This + // added complexity is only for the X and W chromosome types, however; a regular diploid autosome is also + // allowed to have null haplosomes and be hemizygous, but that situation does not receive hemizygous effect + // caches in the present design since it is not clear that it would be a win in most cases (although for + // haplodiploid models it probably would be, in fact). FIXME MULTITRAIT: Maybe we can be smarter on this... // - // Mutation runs are considered to be immutable in SLiM if they are referred to by more than one haplosome. - // If they are referred to only once, however, they can be changed. What that occurs, their nonneutral - // cache must be invalidated. This means that any code that calls use_count() on a mutrun, and modifies it - // if the count is 1, must also invalidate the nonneutral cache. This is done automatically by the existing - // methods – in particular, MutationRun::will_modify_run(), which should be a funnel for all such code. - // Newly created mutation runs are also routinely modified on the (valid) assumption that they are referred - // to by only one haplosome (or no haplosomes at all, more likely); this is fine since they don't have a nonneutral - // cache yet anyway. + // Another special case has to do with the evaluation of traits for neutrality. As described above, mutations + // with non-independent effects on at least one trait have to be put in the non-neutral mutation buffer, so + // that those effects get evaluated correctly (for heterozygous vs. homozygous effects). And normally this + // means that any independent-dominance effects the mutation might have on other traits cannot be optimized, + // since the mutation is in the non-neutral mutation buffer. There is one case where this can be finessed, + // however: when ALL mutations are known to be independent-dominance (or neutral) for a given trait. If SLiM + // can determine that, it can put the effects of all mutations for that trait into the independent-dominance + // effect cache for that trait! This setup needs to be flagged separately, and the code that calculates the + // effects of mutations in the non-neutral mutation buffer needs to see that flag and skip that trait, to + // avoid double-counting the effect of these mutations. This introduces significant additional complexity in + // the design, but it is very much worthwhile since it allows models containing both an independent trait and + // a non-independent trait to run much faster -- and that kind of setup is expected to be fairly common. // - // These caches are only used for mutation runs that are accessed by the FitnessOfParentWithHaplosomeIndices...() - // suite of methods; pure neutral models and non-chromosome-dependent models will never touch these caches - // and the buffer will never be allocated. + // There is a final special case that we do not cater to for now, and that is separate non-neutral mutation + // caching behavior on a per-chromosome basis. Since any given mutation run belongs to one chromosome, we + // can potentially customize the non-neutral caching policy on a per-chromosome basis, catering to the + // possibility that different chromosomes will have different genetic configurations -- one might be neutral + // for trait A while another is non-neutral for trait A, for example. If we kept track of that (not hard), + // we could optimize the caching behavior for each chromosome and potentially get some big wins, like skipping + // all genetic calculations for most of the chromosomes in a model because we can deduce that non-neutral + // effects are only present on a couple of the chromosomes, which seems like it might be common. Optimization + // at this level might be added later. FIXME MULTITRAIT // // BCH 4/19/2023: Note that this stuff is all related to caching, so it is mutable even for immutable objects. - mutable int32_t nonneutral_change_validation_ = 0; // compared to sim.nonneutral_change_counter_ to detect changes - + // BCH 1/14/2025: PLANNED CHANGES: + // + // X remove nonneutral_change_validation_; now that we will pre-validate all non-neutral caches prior to use, + // this is no longer needed and just wastes space. Instead, we will simply have a flag on Species, or + // perhaps per-chromosome, that says "everything is invalid", and we will check that flag and act on it + // at validation time + // - nonneutral_mutations_: this pointer should now point first to the per-trait independent-dominance effects, + // then to the hemizygous per-trait effects if present, then to the MutationIndex buffer, sequentially. + // this is to avoid multiple allocations and multiple pointers, and to maximize memory locality. Since + // this arrangement will be very gross to work with, it should be private only to a set of helper methods + // that mask the implementation details completely. TBD: who exactly will know whether hemizygous effects + // are present, and how many traits exist, and so forth? The chromosome knows all that, or can get to it; + // but we don't have a pointer to the chromosome. Maybe all the non-neutral cache helper methods take a + // Chromosome * parameter to help those methods find their way out? But ugh. I think after we remove + // nonneutral_change_validation_ there will be room in the class layout for a little info: + // bool chromosome_type_is_hemizygous; + // uint8_t species_trait_count; + // That info would be present only when non-neutral caching is enabled. + // - the capacity and count and that info stuff should actually get moved inside the non-neutral cache pointer. + // The rationale for this is that there will be cases where we want to turn non-neutral caching off, + // entirely or per-chromosome, even though it is enabled in the build: if we know that all mutations are + // neutral, for example, or if we know that NO mutations are neutral or independent-dominance, such that + // the non-neutral cache would just be a copy of the main mutation buffer. In such cases, keeping all of + // the non-neutral cache state inside the pointer will allow us to minimize our memory footprint for all + // of the mutation runs in question. + // - to manage this within-pointer complexity we should have a struct, NonNeutralCache, that the pointer + // points to. It would have the fixed state up front, and then the variable-length state after. mutable int32_t nonneutral_mutation_capacity_ = 0; // the capacity of nonneutral_mutations_ mutable int32_t nonneutral_mutations_count_ = -1; // the number of entries currently used; -1 indicates an invalid cache mutable MutationIndex *nonneutral_mutations_ = nullptr; // OWNED POINTER: a pointer to MutationIndex for non-neutral mutations -#if (SLIMPROFILING == 1) -// PROFILING - mutable bool recached_run_ = false; // so SLiMgui can count how many nonneutral caches get recached each tick -#endif // (SLIMPROFILING == 1) - -#endif // SLIM_USE_NONNEUTRAL_CACHES +#endif // SLIM_USE_NONNEUTRAL_CACHES() public: @@ -306,7 +414,7 @@ class MutationRun freed_run->mutation_count_ = 0; // empty the mutation buffer -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_USE_NONNEUTRAL_CACHES() freed_run->nonneutral_mutations_count_ = -1; // mark the non-neutral mutation cache as invalid #endif @@ -377,7 +485,7 @@ class MutationRun } inline __attribute__((always_inline)) void will_modify_run(void) { -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_USE_NONNEUTRAL_CACHES() nonneutral_mutations_count_ = -1; // invalidate the nonneutral cache since the run is changing #endif } @@ -696,123 +804,72 @@ class MutationRun // splitting mutation runs void split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const; -#if SLIM_USE_NONNEUTRAL_CACHES - // caching non-neutral mutations; see above for comments about the "regime" etc. - -// inline __attribute__((always_inline)) void zero_out_nonneutral_buffer(void) const -// { -// if (!nonneutral_mutations_) -// { -// // If we don't have a buffer allocated yet, follow the same rules as for the main mutation buffer -// nonneutral_mutation_capacity_ = SLIM_MUTRUN_INITIAL_CAPACITY; -// nonneutral_mutations_ = (MutationIndex *)malloc(nonneutral_mutation_capacity_ * sizeof(MutationIndex)); -// if (!nonneutral_mutations_) -// EIDOS_TERMINATION << "ERROR (MutationRun::zero_out_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); -// } -// -// // empty out the current buffer contents -// nonneutral_mutations_count_ = 0; -// } -// -// inline __attribute__((always_inline)) void add_to_nonneutral_buffer(MutationIndex p_mutation_index) const -// { -// // This is basically the emplace_back() code, but for the nonneutral buffer -// if (nonneutral_mutations_count_ == nonneutral_mutation_capacity_) -// { -//#ifdef __clang_analyzer__ -// assert(nonneutral_mutation_capacity_ > 0); -//#endif -// -// if (nonneutral_mutation_capacity_ < 32) -// nonneutral_mutation_capacity_ <<= 1; // double the number of pointers we can hold -// else -// nonneutral_mutation_capacity_ += 16; -// -// nonneutral_mutations_ = (MutationIndex *)realloc(nonneutral_mutations_, nonneutral_mutation_capacity_ * sizeof(MutationIndex)); -// if (!nonneutral_mutations_) -// EIDOS_TERMINATION << "ERROR (MutationRun::add_to_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); -// } -// -// *(nonneutral_mutations_ + nonneutral_mutations_count_) = p_mutation_index; -// ++nonneutral_mutations_count_; -// } -// -// void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const; -// void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const; -// void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const; -// -// void check_nonneutral_mutation_cache() const; -// -// inline __attribute__((always_inline)) void beginend_nonneutral_pointers(Mutation *p_mut_block_ptr, const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const -// { -// if ((nonneutral_change_validation_ != p_nonneutral_change_counter) || (nonneutral_mutations_count_ == -1)) -// { -// // When running parallel, all nonneutral caches must be validated -// // ahead of time; see Subpopulation::FixNonNeutralCaches_OMP() -// THREAD_SAFETY_IN_ACTIVE_PARALLEL("beginend_nonneutral_pointers()"); -// -// // If the nonneutral change counter has changed since we last validated, or our cache is invalid for other -// // reasons (most notably being a new mutation run that has not yet cached), validate it immediately -// nonneutral_change_validation_ = p_nonneutral_change_counter; -// -// switch (p_nonneutral_regime) -// { -// case 1: cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr); break; -// case 2: cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr); break; -// case 3: cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr); break; -// } -// -//#if (SLIMPROFILING == 1) -// // PROFILING -// recached_run_ = true; -//#endif -// } -// -//#if DEBUG -// check_nonneutral_mutation_cache(); -//#endif -// -// // Return the requested pointers to allow the caller to iterate over the nonneutral mutation buffer -// *p_mutptr_iter = nonneutral_mutations_; -// *p_mutptr_max = nonneutral_mutations_ + nonneutral_mutations_count_; -// } -// -//#ifdef _OPENMP -// // This is used by Subpopulation::FixNonNeutralCaches_OMP() to validate -// // these caches; it starts a new task if the nonneutral cache is invalid -// // This method is called from within a "single" construct. -// inline __attribute__((always_inline)) void validate_nonneutral_cache(int32_t p_nonneutral_change_counter, int32_t p_nonneutral_regime) const -// { -// if ((nonneutral_change_validation_ != p_nonneutral_change_counter) || (nonneutral_mutations_count_ == -1)) -// { -// // If the nonneutral change counter has changed since we last validated, or our cache is invalid for other -// // reasons (most notably being a new mutation run that has not yet cached), validate it with an OpenMP task -// // We set up these variables to prevent ourselves from seeing the cache as invalid again -// nonneutral_change_validation_ = p_nonneutral_change_counter; -// nonneutral_mutations_count_ = 0; -// -//#if (SLIMPROFILING == 1) -// // PROFILING -// recached_run_ = true; -//#endif -// -// // I tried splitting the below code out into its own non-inline method, -// // but that seemed to trigger a compiler bug, so here we are. -//#pragma omp task -// { -// switch (p_nonneutral_regime) -// { -// case 1: cache_nonneutral_mutations_REGIME_1(); break; -// case 2: cache_nonneutral_mutations_REGIME_2(); break; -// case 3: cache_nonneutral_mutations_REGIME_3(); break; -// } -// } -// } -// } -//#endif - -#if (SLIMPROFILING == 1) +#if SLIM_USE_NONNEUTRAL_CACHES() + // caching non-neutral mutations; see above for comments about how this works + + // note this method does NOT check external invalidation flags! it tells you only if the mutrun itself knows it is invalid! + inline __attribute__((always_inline)) bool nonneutral_cache_invalid(void) const { return (nonneutral_mutations_count_ == -1); } + + inline __attribute__((always_inline)) void zero_out_nonneutral_buffer(void) const + { + if (!nonneutral_mutations_) + { + // If we don't have a buffer allocated yet, follow the same rules as for the main mutation buffer + nonneutral_mutation_capacity_ = SLIM_MUTRUN_INITIAL_CAPACITY; + nonneutral_mutations_ = (MutationIndex *)malloc(nonneutral_mutation_capacity_ * sizeof(MutationIndex)); + if (!nonneutral_mutations_) + EIDOS_TERMINATION << "ERROR (MutationRun::zero_out_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + } + + // empty out the current buffer contents + nonneutral_mutations_count_ = 0; + } + + inline __attribute__((always_inline)) void add_to_nonneutral_buffer(MutationIndex p_mutation_index) const + { + // This is basically the emplace_back() code, but for the nonneutral buffer + if (nonneutral_mutations_count_ == nonneutral_mutation_capacity_) + { +#ifdef __clang_analyzer__ + assert(nonneutral_mutation_capacity_ > 0); +#endif + + if (nonneutral_mutation_capacity_ < 32) + nonneutral_mutation_capacity_ <<= 1; // double the number of pointers we can hold + else + nonneutral_mutation_capacity_ += 16; + + nonneutral_mutations_ = (MutationIndex *)realloc(nonneutral_mutations_, nonneutral_mutation_capacity_ * sizeof(MutationIndex)); + if (!nonneutral_mutations_) + EIDOS_TERMINATION << "ERROR (MutationRun::add_to_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + } + + *(nonneutral_mutations_ + nonneutral_mutations_count_) = p_mutation_index; + ++nonneutral_mutations_count_; + } + + void cache_nonneutral_mutations_REGIME_0(void) const; + void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const; + void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const; + void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const; + + void check_nonneutral_mutation_cache() const; + + inline __attribute__((always_inline)) void beginend_nonneutral_pointers(const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max) const + { +#if DEBUG + // All nonneutral caches must be validated ahead of time; see Species::ValidateNonNeutralCaches() + check_nonneutral_mutation_cache(); +#endif + + // Return the requested pointers to allow the caller to iterate over the nonneutral mutation buffer + *p_mutptr_iter = nonneutral_mutations_; + *p_mutptr_max = nonneutral_mutations_ + nonneutral_mutations_count_; + } + +#if SLIM_PROFILE_NONNEUTRAL_CACHES() // PROFILING + // FIXME MULTITRAIT: I think maybe this gets absorbed into Species::ValidateNonNeutralCaches()? inline __attribute__((always_inline)) void tally_nonneutral_mutations(int64_t *p_mutation_count, int64_t *p_nonneutral_count, int64_t *p_recached_count) const { *p_mutation_count += mutation_count_; @@ -826,9 +883,9 @@ class MutationRun recached_run_ = false; } } -#endif // (SLIMPROFILING == 1) +#endif // SLIM_PROFILE_NONNEUTRAL_CACHES() -#endif // SLIM_USE_NONNEUTRAL_CACHES +#endif // SLIM_USE_NONNEUTRAL_CACHES() // Memory usage tallying, for outputUsage() size_t MemoryUsageForMutationIndexBuffers(void) const; diff --git a/core/mutation_type.h b/core/mutation_type.h index f78572d7..59d6e4ec 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -182,34 +182,35 @@ class MutationType : public EidosDictionaryUnretained // change back from false to true if the DES changes from non-neutral back to neutral. mutable bool all_neutral_DES_; - // all_pure_neutral_mutations_ is true if any mutation of this type could be non-neutral. That is the case - // if (a) the mutation type has ever had a non-neutral DES, or (b) if any mutation of this type has ever been - // configured to be non-neutral. This flag is "sticky"; once set to true it will remain true forever. + // muttype_all_neutral_mutations_ is false if any mutation of this type could be intrinsically non-neutral, + // ignoring the effects of callbacks. That is the case if (a) the mutation type has ever had a non-neutral + // DES, or (b) if any mutation of this type has ever been configured to be non-neutral. This flag is + // "sticky"; once set to true it will remain true forever. mutable bool muttype_all_neutral_mutations_; - // is_pure_neutral_now_ is set up by Subpopulation::UpdateFitness(), and is valid only inside a given UpdateFitness() call. - // If set, it indicates that the mutation type is currently pure neutral – either because all_neutral_DES_ is set and the - // mutation type cannot be influenced by any callbacks in the current subpopulation / tick, or because an active callback - // actually sets the mutation type to be a constant value of 1.0 in this subpopulation / tick. Mutations for which this - // flag is set can be safely elided from fitness calculations altogether; the flag will not be set if other active callbacks - // could mess things up for the mutation type by, e.g., deactivating the neutral-making callback. If this flag is set for all - // muttypes, chromosome-based fitness calculations will be skipped altogether for this tick. + + // Optimization flags set up by Species::PrepareForTraitCalculations() and valid only subsequent to that call. + // These flags are used to determine which mutations of this type go into the non-neutral cache. The previous + // flags are used as scratch space by PrepareForTraitCalculations() and should not otherwise be used. + + // If set, it indicates that the mutation type is currently completely neutral, including callbacks – either + // because muttype_all_neutral_mutations_ is set and the mutation type cannot be influenced by any callbacks + // in the current subpopulation / tick, or because an active callback actually sets this mutation type to be + // neutral in this subpopulation / tick. Mutation types for which this flag is set can be safely elided from + // trait calculations altogether. If this flag is set for all muttypes, chromosome-based trait calculations + // will be skipped altogether for this tick. mutable bool is_pure_neutral_now_; + mutable bool previous_is_pure_neutral_now_; + + // If set, subject_to_mutationEffect_callback_ indicates that the muttype is influenced by a mutationEffect + // callback in at least one subpop. Mutation types with this flag set are subject to a callback, and so the + // effect of mutations of that type cannot be consulted reliably; the callback needs to be considered. + mutable bool subject_to_mutationEffect_callback_; + mutable bool previous_subject_to_mutationEffect_callback_; + + mutable bool subject_to_non_neutral_callback_; + mutable bool previous_subject_to_non_neutral_callback_; - // set_neutral_by_global_active_callback_ is set by RecalculateFitness() if the muttype is made neutral by a constant callback - // (i.e., return 1.0) that is global (i.e., applies to all subpops) and active. This flag should be consulted only when the - // "nonneutral regime" (i.e., sim.last_nonneutral_regime_) is 2 (constant neutral mutationEffect() callbacks only); it is not - // valid in other scenarios, so it should be used with extreme caution. - mutable bool set_neutral_by_global_active_callback_ = false; - mutable bool previous_set_neutral_by_global_active_callback_; // the previous value; scratch space for RecalculateFitness() - - // subject_to_mutationEffect_callback_ is set by RecalculateFitness() if the muttype is currently influenced by a callback in any subpop. - // Mutations with this flag set are considered to be non-neutral, since their fitness value is unpredictable; mutations without - // this flag set, on the other hand, are not influenced by any callback (active or inactive), so their effect may be consulted. - // This flag is valid only when the "nonneutral regime" (i.e., sim.last_nonneutral_regime_) is 3 (non-constant or non-neutral - // callbacks present); it is not valid in other scenarios, so it should be used with extreme caution. - mutable bool subject_to_mutationEffect_callback_ = false; - mutable bool previous_subject_to_mutationEffect_callback_; // the previous value; scratch space for RecalculateFitness() #ifdef SLIMGUI int mutation_type_index_; // a zero-based index for this mutation type, used by SLiMgui to bin data by mutation type diff --git a/core/population.cpp b/core/population.cpp index 12094a05..f3648cd1 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -40,9 +40,9 @@ #include "mutation_block.h" #include "eidos_globals.h" -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() #include "robin_hood.h" -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() #include #endif @@ -732,7 +732,7 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind { if (mate_choice_callback->block_active_) { -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -1165,7 +1165,7 @@ bool Population::ApplyModifyChildCallbacks(Individual *p_child, Individual *p_pa { if (modify_child_callback->block_active_) { -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -2777,7 +2777,7 @@ bool Population::ApplyRecombinationCallbacks(Individual *p_parent, Haplosome *p_ continue; } -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -2933,7 +2933,7 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h Haplosome::DebugCheckStructureMatch(parent_haplosome_1, parent_haplosome_2, &p_child_haplosome, &p_chromosome); #endif -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // start with a clean slate in the child haplosome; we now expect child haplosomes to be cleared for us p_child_haplosome.check_cleared_to_nullptr(); #endif @@ -3795,7 +3795,7 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha Haplosome::DebugCheckStructureMatch(parent_haplosome, &p_child_haplosome, &p_chromosome); #endif -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // start with a clean slate in the child haplosome; we now expect child haplosomes to be cleared for us p_child_haplosome.check_cleared_to_nullptr(); #endif @@ -4072,7 +4072,7 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil Haplosome::DebugCheckStructureMatch(parent_haplosome_1, parent_haplosome_2, &p_child_haplosome, &p_chromosome); #endif -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // start with a clean slate in the child haplosome; we now expect child haplosomes to be cleared for us p_child_haplosome.check_cleared_to_nullptr(); #endif @@ -5279,199 +5279,28 @@ void Population::RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recal // as per the SLiM design spec, we get the list of callbacks once, and use that list throughout this stage, but we construct // subsets of it for each subpopulation, so that UpdateFitness() can just use the callback list as given to it std::vector mutationEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); - std::vector fitnessEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); - bool no_active_callbacks = (mutationEffect_callbacks.size() == 0) && (fitnessEffect_callbacks.size() == 0); - - // FIXME MULTITRAIT: the "regime" logic here needs to adapt to the fact that we only fetch active callbacks now; it is more conservative than it now needs to be! - // Figure out how we are going to handle MutationRun nonneutral mutation caches; see mutation_run.h. We need to assess - // the state of callbacks and decide which of the three "regimes" we are in, and then depending upon that and what - // regime we were in in the previous generation, invalidate nonneutral caches or allow them to persist. - const std::map &mut_types = species_.MutationTypes(); - int32_t last_regime = species_.last_nonneutral_regime_; - int32_t current_regime; - - if (no_active_callbacks) - { - current_regime = 1; - } - else - { - // First, we want to save off the old values of our flags that govern nonneutral caching - for (auto muttype_iter : mut_types) - { - MutationType *muttype = muttype_iter.second; - - muttype->previous_set_neutral_by_global_active_callback_ = muttype->set_neutral_by_global_active_callback_; - muttype->previous_subject_to_mutationEffect_callback_ = muttype->subject_to_mutationEffect_callback_; - } - - // Then we assess which muttypes are being made globally neutral by a constant-value mutationEffect() callback - bool all_active_callbacks_are_global_neutral_effects = true; - - for (auto muttype_iter : mut_types) - (muttype_iter.second)->set_neutral_by_global_active_callback_ = false; - - for (SLiMEidosBlock *mutationEffect_callback : mutationEffect_callbacks) - { - if (mutationEffect_callback->block_active_) - { - if (mutationEffect_callback->subpopulation_id_ == -1) - { - const EidosASTNode *compound_statement_node = mutationEffect_callback->compound_statement_node_; - - if (compound_statement_node->cached_return_value_) - { - // The script is a constant expression such as "{ return 1.1; }" - EidosValue *result = compound_statement_node->cached_return_value_.get(); - - if ((result->Type() == EidosValueType::kValueFloat) && (result->Count() == 1)) - { - if (result->FloatData()[0] == 1.0) - { - // the callback returns 1.0, so it makes the mutation types to which it applies become neutral - slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; - - if (mutation_type_id != -1) - { - MutationType *found_muttype = species_.MutationTypeWithID(mutation_type_id); - - if (found_muttype) - found_muttype->set_neutral_by_global_active_callback_ = true; - } - - // This is a constant neutral effect, so avoid dropping through to the flag set below - continue; - } - } - } - } - - // if we reach this point, we have an active callback that is not a - // global constant neutral effect, so set our flag and break out - all_active_callbacks_are_global_neutral_effects = false; - break; - } - } - - if (all_active_callbacks_are_global_neutral_effects) - { - // The only active callbacks are global (i.e. not subpop-specific) constant-effect neutral callbacks, - // so we will use the set_neutral_by_global_active_callback flag in the muttypes that we set up above. - // When that flag is true, the mut is neutral; when it is false, consult the selection coefficient. - current_regime = 2; - } - else - { - // We have at least one active callback that is not a global constant-effect callback, so all - // bets are off; any mutation of a muttype influenced by a callback must be considered non-neutral, - // as governed by the flag set up below - current_regime = 3; - - for (auto muttype_iter : mut_types) - (muttype_iter.second)->subject_to_mutationEffect_callback_ = false; - - for (SLiMEidosBlock *mutationEffect_callback : mutationEffect_callbacks) - { - slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; - - if (mutation_type_id != -1) - { - MutationType *found_muttype = species_.MutationTypeWithID(mutation_type_id); - - if (found_muttype) - found_muttype->subject_to_mutationEffect_callback_ = true; - } - } - } - } +#if DEBUG_TRAIT_DEMAND() + std::cout << "# " << community_.Tick() << " ====== RecalculateFitness(): forceRecalc == " << (p_force_trait_recalculation ? "T" : "F") << std::endl; +#endif - // trigger a recache of nonneutral mutation lists for some regime transitions; see mutation_run.h - if (last_regime == 0) // NOLINTNEXTLINE(*-branch-clone) : intentional branch clones - species_.nonneutral_change_counter_++; - else if ((current_regime == 1) && ((last_regime == 2) || (last_regime == 3))) - species_.nonneutral_change_counter_++; - else if (current_regime == 2) - { - if (last_regime != 2) - species_.nonneutral_change_counter_++; - else - { - // If we are in regime 2 this cycle and were last cycle as well, then if the way that - // mutationEffect() callbacks are influencing mutation types is the same this cycle as it was last - // cycle, we can actually carry over our nonneutral buffers. - bool callback_state_identical = true; - - for (auto muttype_iter : mut_types) - { - MutationType *muttype = muttype_iter.second; - - if (muttype->set_neutral_by_global_active_callback_ != muttype->previous_set_neutral_by_global_active_callback_) - callback_state_identical = false; - } - - if (!callback_state_identical) - species_.nonneutral_change_counter_++; - } - } - else if (current_regime == 3) - { - if (last_regime != 3) - species_.nonneutral_change_counter_++; - else - { - // If we are in regime 3 this cycle and were last cycle as well, then if the way that - // mutationEffect() callbacks are influencing mutation types is the same this cycle as it was last - // cycle, we can actually carry over our nonneutral buffers. - bool callback_state_identical = true; - - for (auto muttype_iter : mut_types) - { - MutationType *muttype = muttype_iter.second; - - if (muttype->subject_to_mutationEffect_callback_ != muttype->previous_subject_to_mutationEffect_callback_) - callback_state_identical = false; - } - - if (!callback_state_identical) - species_.nonneutral_change_counter_++; - } - } + species_.PrepareForTraitCalculations(mutationEffect_callbacks); - // move forward to the regime we just chose; UpdateFitness() can consult this to get the current regime - species_.last_nonneutral_regime_ = current_regime; // we need to recalculate phenotypes for traits that have a direct effect on fitness std::vector p_direct_effect_trait_indices; const std::vector &traits = species_.Traits(); for (slim_trait_index_t trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) - { - Trait *trait = traits[trait_index]; - - if (trait->HasDirectFitnessEffect()) - { - // Sneaky optimization: we might know that all mutations are completely neutral for this trait; - // if so, we can exclude it from the list of direct-effect traits and skip the demand for its - // phenotypes. This will mean that fitness recalculation does not necessarily demand phenotypes - // for all direct-effect traits, which might be surprising to the user and should be documented. - // See also the optimization for trait_all_mutations_independent_dominance_, which is done inside - // non-neutral cache validation since the effects for such traits go into that cache. - if (trait->trait_all_neutral_mutations_) - { -#if DEBUG_TRAIT_DEMAND - std::cout << "# " << community_.Tick() << " --- RecalculateFitness() removed demand for direct-effect trait '" << trait->Name() << "' because it is known to be neutral" << std::endl; -#endif - - continue; - } - + if (traits[trait_index]->HasDirectFitnessEffect()) p_direct_effect_trait_indices.push_back(trait_index); - } - } SLiMEidosBlockType old_executing_block_type = community_.executing_block_type_; community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMutationEffectCallback; // used for both mutationEffect() and fitnessEffect() for simplicity // FIXME MULTITRAIT: this will get cleaned up when multiple phenotypes is done + + std::vector fitnessEffect_callbacks = species_.CallbackBlocksMatching(p_tick, SLiMEidosBlockType::SLiMEidosFitnessEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); + bool no_active_callbacks = (mutationEffect_callbacks.size() == 0) && (fitnessEffect_callbacks.size() == 0); + // call UpdateFitness() for each subpopulation if (no_active_callbacks) { @@ -5535,7 +5364,7 @@ void Population::RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recal //Individual::s_any_individual_fitness_scaling_set_ = false; } -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // WF only: // Clear all parental haplosomes to use nullptr for their mutation runs, so they are ready to reuse in the next tick // BCH 10/15/2024: This is now only enabled as a debugging setting; clearing haplosomes is no longer necessary. @@ -5805,13 +5634,13 @@ void Population::SplitMutationRunsForChromosome(int32_t p_new_mutrun_count, Chro if (new_mutrun_count <= SLIM_HAPLOSOME_MUTRUN_BUFSIZE) { haplosome->mutruns_ = haplosome->run_buffer_; -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() EIDOS_BZERO(haplosome->run_buffer_, SLIM_HAPLOSOME_MUTRUN_BUFSIZE * sizeof(const MutationRun *)); #endif } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() haplosome->mutruns_ = (const MutationRun **)calloc(new_mutrun_count, sizeof(const MutationRun *)); #else haplosome->mutruns_ = (const MutationRun **)malloc(new_mutrun_count * sizeof(const MutationRun *)); @@ -5826,10 +5655,10 @@ void Population::SplitMutationRunsForChromosome(int32_t p_new_mutrun_count, Chro } // make a map to keep track of which mutation runs split into which new runs -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map> split_map; //typedef robin_hood::pair> SLiM_SPLIT_PAIR; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map> split_map; //typedef std::pair> SLiM_SPLIT_PAIR; #endif @@ -5951,10 +5780,10 @@ void Population::SplitMutationRunsForChromosome(int32_t p_new_mutrun_count, Chro struct slim_pair_hash { template std::size_t operator () (const std::pair &p) const { -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() auto h1 = robin_hood::hash{}(p.first); auto h2 = robin_hood::hash{}(p.second); -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() auto h1 = std::hash{}(p.first); auto h2 = std::hash{}(p.second); #endif @@ -6016,13 +5845,13 @@ void Population::JoinMutationRunsForChromosome(int32_t p_new_mutrun_count, Chrom if (new_mutrun_count <= SLIM_HAPLOSOME_MUTRUN_BUFSIZE) { haplosome->mutruns_ = haplosome->run_buffer_; -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() EIDOS_BZERO(haplosome->run_buffer_, SLIM_HAPLOSOME_MUTRUN_BUFSIZE * sizeof(const MutationRun *)); #endif } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() haplosome->mutruns_ = (const MutationRun **)calloc(new_mutrun_count, sizeof(const MutationRun *)); #else haplosome->mutruns_ = (const MutationRun **)malloc(new_mutrun_count * sizeof(const MutationRun *)); @@ -6037,10 +5866,10 @@ void Population::JoinMutationRunsForChromosome(int32_t p_new_mutrun_count, Chrom } // make a map to keep track of which mutation runs join into which new runs -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map, const MutationRun *, slim_pair_hash> join_map; //typedef robin_hood::pair, const MutationRun *> SLiM_JOIN_PAIR; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map, const MutationRun *, slim_pair_hash> join_map; //typedef std::pair, const MutationRun *> SLiM_JOIN_PAIR; #endif @@ -7639,7 +7468,7 @@ void Population::RemoveAllFixedMutations(void) // a substitution object was already created by removeMutations() at the user's request; // the refcount is zero because the mutation was removed in script, but it was fixed/substituted // this code path is similar to the fixation code path below, but does not create a Substitution -#if DEBUG_MUTATIONS +#if DEBUG_MUTATIONS() std::cout << "Mutation fixed by script, already substituted: " << mutation << std::endl; #endif @@ -7662,7 +7491,7 @@ void Population::RemoveAllFixedMutations(void) } else { -#if DEBUG_MUTATIONS +#if DEBUG_MUTATIONS() std::cout << "Mutation unreferenced, will remove: " << mutation << std::endl; #endif @@ -7682,7 +7511,7 @@ void Population::RemoveAllFixedMutations(void) { if (mutation->mutation_type_ptr_->convert_to_substitution_) { -#if DEBUG_MUTATIONS +#if DEBUG_MUTATIONS() std::cout << "Mutation fixed, will substitute: " << mutation << std::endl; #endif diff --git a/core/population.h b/core/population.h index 16655a83..433be275 100644 --- a/core/population.h +++ b/core/population.h @@ -308,7 +308,7 @@ class Population // step forward a generation: make the children become the parents void SwapGenerations(void); -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // Clear all parental haplosomes to use nullptr for their mutation runs, so they are ready to reuse in the next tick void ClearParentalHaplosomes(void); #endif diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index a7db2b89..0bddb1c8 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -110,7 +110,7 @@ void SLiM_WarmUp(void) gStaticEidosValue_StringG = EidosValue_String_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_G)); gStaticEidosValue_StringT = EidosValue_String_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_T)); -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() // Check for a memory limit and prepare for memory-limit testing Eidos_CheckRSSAgainstMax("SLiM_WarmUp()", "This internal check should never fail!"); #endif @@ -2494,7 +2494,7 @@ void WriteProfileResults(std::string profile_output_path, std::string model_name } -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() // // MutationRun metrics, presented per Species // diff --git a/core/slim_globals.h b/core/slim_globals.h index 1c825ffe..0cee1e18 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -388,7 +388,8 @@ void Eidos_Deleter(void *ptr) { */ -#define DEBUG_MUTATIONS 0 // turn on logging of mutation construction and destruction +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define DEBUG_MUTATIONS() 0 // turn on logging of mutation construction and destruction // Per-species memory usage assessment as done by Species::TabulateSLiMMemoryUsage_Species() is placed into this struct typedef struct @@ -487,14 +488,20 @@ void AccumulateMemoryUsageIntoTotal_Community(SLiMMemoryUsage_Community &p_usage #pragma mark - // Debugging #defines that can be turned on +// +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ #define DEBUG_MUTATION_ZOMBIES 0 // avoid destroying Mutation objects; keep them as zombies #define SLIM_DEBUG_MUTATION_RUNS 0 // turn on to get logging about mutation run uniquing and usage #define DEBUG_BLOCK_REG_DEREG 0 // turn on to get logging about script block registration/deregistration -#define DEBUG_SHUFFLE_BUFFER 1 // debug memory overruns with the shuffle buffer +#define DEBUG_SHUFFLE_BUFFER() 1 // debug memory overruns with the shuffle buffer #define DEBUG_TICK_RANGES 0 // debug tick range parsing and evaluation -#define DEBUG_TRAIT_DEMAND 0 // enable debugging logs about the trait "demand" evalutation process +#define DEBUG_TRAIT_DEMAND() 0 // enable debugging logs about the trait "demand" evalutation process #define DEBUG_LESS_INTENSIVE 0 // decrease the frequency of some very intensive DEBUG checks +#if DEBUG_TRAIT_DEMAND() +#warning DEBUG_TRAIT_DEMAND() enabled! +#endif + // In SLiMgui we want to emit only a reasonably limited number of lines of input debugging; for big models, this output // can get rather excessive. Outside of SLiMgui, though, we emit it all, because the user might need it for some reason. @@ -508,10 +515,12 @@ void AccumulateMemoryUsageIntoTotal_Community(SLiMMemoryUsage_Community &p_usage // where we are particularly likely to run out of memory, to provide the user with a better error message. // Note that even when this is 1, the user can disable some of these checks with -x. // Disable for Windows until Eidos_GetMaxRSS() issue fixed: +// +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ #ifdef _WIN32 -#define DO_MEMORY_CHECKS 0 +#define DO_MEMORY_CHECKS() 0 #else -#define DO_MEMORY_CHECKS 1 +#define DO_MEMORY_CHECKS() 1 #endif // If 1, and SLiM_verbosity_level >= 2, additional output will be generated regarding the mutation run count @@ -525,7 +534,9 @@ void AccumulateMemoryUsageIntoTotal_Community(SLiMMemoryUsage_Community &p_usage // freed, or disposed of into a junkyard, or anything like that -- whenever it is no longer in use. This // could be useful for debugging problems with dereferencing stale MutationRun pointers. Otherwise it is // not necessary, and just slows SLiM down. -#define SLIM_CLEAR_HAPLOSOMES 0 +// +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define SLIM_CLEAR_HAPLOSOMES() 0 // Verbosity, from the command-line option -l[ong]; defaults to 1 if -l[ong] is not used extern int64_t SLiM_verbosity_level; diff --git a/core/species.cpp b/core/species.cpp index 9d72b376..0af4dd26 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -52,7 +52,7 @@ #include #include "eidos_globals.h" -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() #include "robin_hood.h" #endif @@ -441,6 +441,581 @@ void Species::_NoteNonNeutralMutation(const Mutation *p_mut) } } +void Species::PrepareForTraitCalculations(std::vector &mutationEffect_callbacks) +{ + // There is nothing to validate, in no-genetics models. + if (!HasGenetics()) + return; + + // First, figure out how we are going to handle MutationRun nonneutral mutation caches; see mutation_run.h. + // We need to assess the state of callbacks for each mutation type, and then depending upon that compared to + // the state from the previous generation, invalidate nonneutral caches or allow them to persist. + + const std::map &mut_types = MutationTypes(); + const std::vector &traits = Traits(); + TraitCalculationRegime last_trait_calculation_regime = current_trait_calculation_regime_; + TraitCalculationRegime new_trait_calculation_regime = TraitCalculationRegime::kUndefined; + + // First, we want to save off the old values of our flags that govern nonneutral caching + for (auto muttype_iter : mut_types) + { + MutationType *muttype = muttype_iter.second; + + muttype->previous_is_pure_neutral_now_ = muttype->is_pure_neutral_now_; + muttype->previous_subject_to_mutationEffect_callback_ = muttype->subject_to_mutationEffect_callback_; + muttype->previous_subject_to_non_neutral_callback_ = muttype->subject_to_non_neutral_callback_; + } + + // Initially, every mutation type is assumed to be uninfluenced by callbacks, and thus pure neutral + // if all mutations of that type are intrinsically neutral (i.e., have an effect of 0.0) + for (auto muttype_iter : mut_types) + { + MutationType *muttype = muttype_iter.second; + + muttype->is_pure_neutral_now_ = muttype->muttype_all_neutral_mutations_; + muttype->subject_to_mutationEffect_callback_ = false; + muttype->subject_to_non_neutral_callback_ = false; + } + + // Similarly, every trait is assumed to be uninfluenced by callbacks, and thus pure neutral if all + // mutations' effects are intrinsically neutral for that trait (i.e., have an effect of 0.0) + for (Trait *trait : traits) + { + trait->is_pure_neutral_now = trait->trait_all_neutral_mutations_; + trait->subject_to_mutationEffect_callback_ = false; + trait->subject_to_non_neutral_callback_ = false; + } + + if (mutationEffect_callbacks.size() == 0) + { + // This regime is "no active callbacks, so you don't have to look at the mutation type at all, it doesn't + // matter; just look at the mutations themselves to determine which go into the non-neutral cache". + // This avoids the work of looking at the mutation type, and also allows the cache to be more precise + // since determinations are made per-mutation rather than per-mutation-type. + new_trait_calculation_regime = TraitCalculationRegime::kNoActiveCallbacks; + } + else + { + // Assess how muttypes are being influenced by mutationEffect() callbacks + for (SLiMEidosBlock *mutationEffect_callback : mutationEffect_callbacks) + { + MutationType *callback_mut_type = nullptr; + bool makes_neutral = _CallbackMakesMutationTypeNeutral(mutationEffect_callback, callback_mut_type); + + // a callback might be defined for a muttype not in use, in which case callback_mut_type is nullptr + if (!callback_mut_type) + continue; + + if (makes_neutral) + { + // the callback makes the mutation type completely neutral, in all subpops; this does not + // entirely override a previous non-neutral callback, because that might have side effects + // on aspects of the model of than the mutation effect, but effect is guaranteed neutral now + callback_mut_type->is_pure_neutral_now_ = true; + callback_mut_type->subject_to_mutationEffect_callback_ = true; + } + else + { + // the callback has an effect that is something other than globally neutral, so the callback + // will have to be called, and it will override anything else going on, including previous + // callbacks in the callback chain (but might be overriden by a later callback in the chain) + callback_mut_type->is_pure_neutral_now_ = false; + callback_mut_type->subject_to_mutationEffect_callback_ = true; + callback_mut_type->subject_to_non_neutral_callback_ = true; + } + } + + // And assess how traits are being influenced by mutationEffect() callbacks + for (SLiMEidosBlock *mutationEffect_callback : mutationEffect_callbacks) + { + Trait *callback_trait = nullptr; + bool makes_neutral = _CallbackMakesTraitNeutral(mutationEffect_callback, callback_trait); + + // callbacks are always specific to one mutation type, so unless there is only one mutation type + // defined, we can't easily infer the the trait has been made entirely neutral; we can just infer + // that it might make a previously neutral trait non-neutral. + if (mut_types.size() == 1) + { + if (callback_trait) + { + callback_trait->subject_to_mutationEffect_callback_ = true; + callback_trait->subject_to_non_neutral_callback_ = !makes_neutral; + callback_trait->is_pure_neutral_now = makes_neutral; + } + else + { + // if no callback trait is defined for the callback, it affects all traits + for (Trait *affectedTrait : traits) + { + affectedTrait->subject_to_mutationEffect_callback_ = true; + affectedTrait->subject_to_non_neutral_callback_ = !makes_neutral; + affectedTrait->is_pure_neutral_now = makes_neutral; + } + } + } + else if (!makes_neutral) + { + // with more than one muttype, callbacks only make traits non-neutral; we don't try to track the + // possibility that multiple callbacks might together render a trait neutral again + if (callback_trait) + { + callback_trait->subject_to_mutationEffect_callback_ = true; + callback_trait->subject_to_non_neutral_callback_ = true; + callback_trait->is_pure_neutral_now = false; + } + else + { + // if no callback trait is defined for the callback, it affects all traits + for (Trait *affectedTrait : traits) + { + affectedTrait->subject_to_mutationEffect_callback_ = true; + affectedTrait->subject_to_non_neutral_callback_ = true; + affectedTrait->is_pure_neutral_now = false; + } + } + } + } + + // Now we would like to know if there is any callback that we actually need to call; if every callback + // is a global-neutral callback, we can skip calling all callbacks completely, but if there exists a + // non-global-neutral callback (even overriden by another callback later in the chain), we have to call + // that since it could have side effects + bool all_active_callbacks_are_global_neutral_effects = true; + + for (auto muttype_iter : mut_types) + { + MutationType *muttype = muttype_iter.second; + + if (muttype->subject_to_non_neutral_callback_) + { + all_active_callbacks_are_global_neutral_effects = false; + break; + } + } + + if (all_active_callbacks_are_global_neutral_effects) + { + // This regime is "we can skip all callbacks, since they are all global-neutral on some or all mutation + // types". We still have to be aware that those global-neutral callbacks exist; we cannot simply + // consult mutation effects, because those are sometimes overridden. But we can assume that if + // subject_to_mutationEffect_callback_ is set for a mutation type, all mutations of that type are + // neutral, because the callback in question is global-neutral. When that flag is not set, we consult + // the mutation itself to decide whether it goes into the non-neutral cache. + new_trait_calculation_regime = TraitCalculationRegime::kAllNeutralCallbacks; + } + else + { + // We have at least one active callback that is not a global constant-effect callback, so there + // is at least one muttype that is influenced by a non-global-neutral callback. We will need to + // take that into account. So we cannot assume that if subject_to_mutationEffect_callback_ is + // set that mutation type is pure neutral; instead, we then have to additionally consult the + // subject_to_non_neutral_callback_ flag. If that is set, we have to call the non-global-neutral + // callback; if it is not set, it is subject to a callback that is not non-neutral, thus it is + // neutral, thus the mutation type is pure neutral. If subject_to_mutationEffect_callback_ is not + // set, that mutation type has no callback, and so we can again consult the mutation itself. + // This regime is thus "we can't skip all callbacks, since some are non-global-neutral, so we have + // to do an extra check and potentially actually call a callback." + new_trait_calculation_regime = TraitCalculationRegime::kNonNeutralCallbacks; + + for (auto muttype_iter : mut_types) + (muttype_iter.second)->subject_to_mutationEffect_callback_ = false; + + for (SLiMEidosBlock *mutationEffect_callback : mutationEffect_callbacks) + { + slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; + + if (mutation_type_id != -1) + { + MutationType *found_muttype = MutationTypeWithID(mutation_type_id); + + if (found_muttype) + found_muttype->subject_to_mutationEffect_callback_ = true; + } + } + } + } + + // If we have no non-global-neutral callbacks, we can potentially know with certainty that the model is + // completely neutral, either because all mutations are intrinsically neutral and there are no callbacks, + // or because all mutation types with non-neutral mutations are made neutral by global-neutral callbacks + // and there are no other callbacks present (even overridden). We detect that special-case situation here. + // This regime is thus "all mutations are effectively neutral; genetics can be skipped." + if ((new_trait_calculation_regime == TraitCalculationRegime::kNoActiveCallbacks) || + (new_trait_calculation_regime == TraitCalculationRegime::kAllNeutralCallbacks)) + { + bool all_muttypes_are_pure_neutral = true; + + for (auto muttype_iter : mut_types) + { + MutationType *muttype = muttype_iter.second; + + if (!muttype->is_pure_neutral_now_) + { + all_muttypes_are_pure_neutral = false; + break; + } + } + + if (all_muttypes_are_pure_neutral) + new_trait_calculation_regime = TraitCalculationRegime::kPureNeutral; + } + + if (new_trait_calculation_regime == TraitCalculationRegime::kUndefined) + EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) nonneutral regime was not decided." << EidosTerminate(); + +#if DEBUG_TRAIT_DEMAND() + std::cout << "# " << community_.Tick() << " +++ PrepareForTraitCalculations() old regime " << current_trait_calculation_regime_ << ", new regime " << new_trait_calculation_regime << std::endl; +#endif + + // Move forward to the regime we just chose; we will consult this next time around to detect a regime change + current_trait_calculation_regime_ = new_trait_calculation_regime; + +#if SLIM_USE_NONNEUTRAL_CACHES() + ValidateNonNeutralCaches(last_trait_calculation_regime); +#endif +} + +bool Species::_CallbackMakesMutationTypeNeutral(SLiMEidosBlock *mutationEffect_callback, MutationType *&mut_type_ptr_ref) +{ + // This method checks whether a mutationEffect() callback makes the mutation type that it refers to neutral. + // The callback has to apply globally: to all subpopulations, and to all traits. We could try to piece the + // effects of multiple callbacks together to see whether, in concert, they make a mutation type neutral, but + // that gets a lot more complicated and I think it would apply to few, if any, real-world models. + slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; + +#if DEBUG + if (mutation_type_id == -1) + EIDOS_TERMINATION << "ERROR (Population::_CallbackMakesMutationTypeNeutral): (internal error) mutationEffect() callback has no mutation type id." << EidosTerminate(); +#endif + + bool makes_neutral = false; // we consider it non-neutral if it doesn't apply to all subpops and traits + + if ((mutationEffect_callback->subpopulation_id_ == -1) && (mutationEffect_callback->trait_index_ == -1)) + { + const EidosASTNode *compound_statement_node = mutationEffect_callback->compound_statement_node_; + + if (compound_statement_node->cached_return_value_) + { + // The script is a constant expression such as "{ return 1.1; }" + EidosValue *result = compound_statement_node->cached_return_value_.get(); + + if ((result->Type() == EidosValueType::kValueFloat) && (result->Count() == 1)) + { + if (result->FloatData()[0] == 1.0) + { + // the callback returns 1.0; this makes the muttype neutral if all traits are multiplicative + makes_neutral = true; + + for (Trait *trait : traits_) + if (trait->Type() == TraitType::kAdditive) { + makes_neutral = false; + break; + } + } + else if (result->FloatData()[0] == 0.0) + { + // the callback returns 0.0; this makes the muttype neutral if all traits are additive + makes_neutral = true; + + for (Trait *trait : traits_) + if (trait->Type() == TraitType::kMultiplicative) { + makes_neutral = false; + break; + } + } + } + else if (result->Type() == EidosValueType::kValueNULL) + { + // the callback returns NULL; this makes the muttype neutral in all cases + makes_neutral = true; + } + } + } + + // find the callback's mutation type; this could be nullptr, if the referenced muttype has not been defined + mut_type_ptr_ref = MutationTypeWithID(mutation_type_id); + + return makes_neutral; +} + +bool Species::_CallbackMakesTraitNeutral(SLiMEidosBlock *mutationEffect_callback, Trait *&trait_ptr_ref) +{ + // This method checks whether a mutationEffect() callback makes the trait that it refers to neutral. + // The callback has to apply globally to all subpopulations (but not necessarily to all traits). + slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; + +#if DEBUG + if (mutation_type_id == -1) + EIDOS_TERMINATION << "ERROR (Population::_CallbackMakesTraitNeutral): (internal error) mutationEffect() callback has no mutation type id." << EidosTerminate(); +#endif + + bool makes_neutral = false; // we consider it non-neutral if it doesn't apply to all subpops + + if (mutationEffect_callback->subpopulation_id_ == -1) + { + const EidosASTNode *compound_statement_node = mutationEffect_callback->compound_statement_node_; + + if (compound_statement_node->cached_return_value_) + { + // The script is a constant expression such as "{ return 1.1; }" + EidosValue *result = compound_statement_node->cached_return_value_.get(); + + if ((result->Type() == EidosValueType::kValueFloat) && (result->Count() == 1)) + { + if (result->FloatData()[0] == 1.0) + { + // the callback returns 1.0; this makes the muttype neutral if all traits are multiplicative + makes_neutral = true; + + for (Trait *trait : traits_) + if (trait->Type() == TraitType::kAdditive) { + makes_neutral = false; + break; + } + } + else if (result->FloatData()[0] == 0.0) + { + // the callback returns 0.0; this makes the muttype neutral if all traits are additive + makes_neutral = true; + + for (Trait *trait : traits_) + if (trait->Type() == TraitType::kMultiplicative) { + makes_neutral = false; + break; + } + } + } + else if (result->Type() == EidosValueType::kValueNULL) + { + // the callback returns NULL; this makes the muttype neutral in all cases + makes_neutral = true; + } + } + } + + // find the callback's trait; this could be nullptr, if the referenced muttype has not been defined + slim_trait_index_t trait_index = mutationEffect_callback->trait_index_; + + if (trait_index == -1) + trait_ptr_ref = nullptr; + else + trait_ptr_ref = traits_[trait_index]; + + return makes_neutral; +} + +#if SLIM_USE_NONNEUTRAL_CACHES() +void Species::ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calculation_regime) +{ + const std::map &mut_types = MutationTypes(); + + // The final caching policy is a combination of the global policy represented by non_neutral_cache_policy + // and the status of each mutation type as represented by the muttype's subject_to_mutationEffect_callback_ + // and is_pure_neutral_now_ flags (the latter being relevant only for some nonneutral cache policy values). + // At this point we need to determine: have we changed final caching policies, compared to the previous tick? + // If so, we will need to throw out all existing non-neutral caches and recache from scratch, because the + // very criteria upon which the existing caches were built has changed. See mutation_run.h. + if (current_trait_calculation_regime_ != last_trait_calculation_regime) + { + // Changing from one regime to another demands a full recache, by definition. + all_nonneutral_caches_invalid_ = true; + } + else if (current_trait_calculation_regime_ == TraitCalculationRegime::kPureNeutral) + { + // This regime is "all mutations are effectively neutral; genetics can be skipped". + // MutationType flags therefore don't matter; whatever the situation might be, we're skipping it all. + } + else if (current_trait_calculation_regime_ == TraitCalculationRegime::kNoActiveCallbacks) + { + // This regime is "no active callbacks, so you don't have to look at the mutation type at all, it doesn't + // matter; just look at the mutations themselves to determine which go into the non-neutral cache". + // MutationType flags therefore don't matter. If mutations changed, that invalidated those runs. + } + else if (current_trait_calculation_regime_ == TraitCalculationRegime::kAllNeutralCallbacks) + { + // This regime is "we can skip all callbacks, since they are all global-neutral on some or all mutation + // types". The *same* mutation types need to be made global-neutral as last time, however, for us to + // carry over any non-neutral caches. We therefore have to check subject_to_mutationEffect_callback_. + bool callback_state_identical = true; + + for (auto muttype_iter : mut_types) + { + MutationType *muttype = muttype_iter.second; + + if (muttype->subject_to_mutationEffect_callback_ != muttype->previous_subject_to_mutationEffect_callback_) + { + callback_state_identical = false; + break; + } + } + + if (!callback_state_identical) + all_nonneutral_caches_invalid_ = true; + } + else if (current_trait_calculation_regime_ == TraitCalculationRegime::kNonNeutralCallbacks) + { + // This regime is "we can't skip all callbacks, since some are non-global-neutral, so we have to do an + // extra check and potentially actually call a callback." Here, subject_to_mutationEffect_callback_ + // and subject_to_non_neutral_callback_ both have to be the same for us to carry over nonneutral caches. + bool callback_state_identical = true; + + for (auto muttype_iter : mut_types) + { + MutationType *muttype = muttype_iter.second; + + if (muttype->subject_to_mutationEffect_callback_ != muttype->previous_subject_to_mutationEffect_callback_) + callback_state_identical = false; + if (muttype->subject_to_non_neutral_callback_ != muttype->previous_subject_to_non_neutral_callback_) + callback_state_identical = false; + } + + if (!callback_state_identical) + all_nonneutral_caches_invalid_ = true; + } + + // Now we do the actual validation of caches, under the newly chosen caching regime +#if (SLIMPROFILING == 1) + // PROFILING + int64_t recached_count = 0; +#endif + + MutationBlock *mutation_block = mutation_block_; + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + + for (Chromosome *chromosome : chromosomes_) + { + bool all_nonneutral_caches_invalid_for_chromosome = all_nonneutral_caches_invalid_; + TraitCalculationRegime trait_calculation_regime_for_chromosome = current_trait_calculation_regime_; + + if (DoingAnyMutationRunExperiments()) chromosome->StartMutationRunExperimentClock(); + + int mutrun_context_count = chromosome->ChromosomeMutationRunContextCount(); + + // FIXME PARALLEL this should be parallelized, one thread per context + for (int mutrun_context_index = 0; mutrun_context_index < mutrun_context_count; ++mutrun_context_index) + { + MutationRunContext &mutrun_context = chromosome->ChromosomeMutationRunContextForThread(mutrun_context_index); + MutationRunPool &mutrun_pool = mutrun_context.in_use_pool_; + + int64_t (Species::*_ValidateNonNeutralCachesForMutationRunPool_TEMPLATED)(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr) = nullptr; + + if (all_nonneutral_caches_invalid_for_chromosome) { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } else { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } + + __attribute__ ((unused)) int64_t this_recached_count = (this->*(_ValidateNonNeutralCachesForMutationRunPool_TEMPLATED))(mutrun_pool, mut_block_ptr); + +#if (SLIMPROFILING == 1) + // PROFILING + recached_count += this_recached_count; +#endif + } + + if (DoingAnyMutationRunExperiments()) chromosome->StopMutationRunExperimentClock("ValidateNonNeutralCaches()"); + } + + all_nonneutral_caches_invalid_ = false; + +#if (SLIMPROFILING == 1) +#warning do something with recached_count here; see old recached_run_ flag code +#endif +} + +template +int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr) +{ + // This applies the specified nonneutral cache regime to all mutation runs in p_mutrun_pool. It's templated + // for efficiency, which might be overkill right now, but I do expect the code complexity here to increase. + size_t mutrun_count = p_mutrun_pool.size(); + const MutationRun **mutrun_pointers = p_mutrun_pool.data(); + + if (f_all_caches_for_pool_invalid) + { + for (size_t mutrun_index = 0; mutrun_index < mutrun_count; ++mutrun_index) + { + const MutationRun *mutrun = mutrun_pointers[mutrun_index]; + + switch (f_nonneutral_cache_regime) + { + case TraitCalculationRegime::kPureNeutral: mutrun->cache_nonneutral_mutations_REGIME_0(); break; + case TraitCalculationRegime::kNoActiveCallbacks: mutrun->cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr); break; + case TraitCalculationRegime::kAllNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr); break; + case TraitCalculationRegime::kNonNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr); break; + default: EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) unrecognized regime." << EidosTerminate(); + } + } + +#if (SLIMPROFILING == 1) + return mutrun_count; +#endif + } + else + { +#if (SLIMPROFILING == 1) + int64_t recached_count = 0; +#endif + for (size_t mutrun_index = 0; mutrun_index < mutrun_count; ++mutrun_index) + { + const MutationRun *mutrun = mutrun_pointers[mutrun_index]; + + if (mutrun->nonneutral_cache_invalid()) + { + switch (f_nonneutral_cache_regime) + { + case TraitCalculationRegime::kPureNeutral: mutrun->cache_nonneutral_mutations_REGIME_0(); break; + case TraitCalculationRegime::kNoActiveCallbacks: mutrun->cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr); break; + case TraitCalculationRegime::kAllNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr); break; + case TraitCalculationRegime::kNonNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr); break; + default: EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) unrecognized regime." << EidosTerminate(); + } + +#if (SLIMPROFILING == 1) + recached_count++; +#endif + } + } +#if (SLIMPROFILING == 1) + return recached_count; +#endif + } + + return 0; // when not profiling, we don't count the number of mutation runs recached +} + +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); + +#endif // SLIM_USE_NONNEUTRAL_CACHES() + + // Chromosome management #pragma mark - #pragma mark Chromosome management @@ -1484,9 +2059,9 @@ slim_tick_t Species::_InitializePopulationFromTextFile(const char *p_file, Eidos EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromTextFile): a Mutations section must follow each Chromosome line." << EidosTerminate(); // Now we are in the Mutations section; read and instantiate all mutations and add them to our map and to the registry -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map mutations; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map mutations; #endif Mutation *mut_block_ptr = mutation_block_->mutation_buffer_; @@ -3304,7 +3879,7 @@ void Species::WF_SwitchToChildGeneration(void) // BCH 10/15/2024: I realized that clearing the haplosomes is no longer needed at all; we can // just remove our requirement that the haplosomes be cleared, and overwrite the stale pointers // when we reuse a haplosome. I am relegating haplosome clearing to a debugging flag. -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() population_.ClearParentalHaplosomes(); #endif } @@ -4651,7 +5226,7 @@ slim_popsize_t *Species::BorrowShuffleBuffer(slim_popsize_t p_buffer_size) if (shuffle_buf_borrowed_) EIDOS_TERMINATION << "ERROR (Species::BorrowShuffleBuffer): (internal error) shuffle buffer already borrowed." << EidosTerminate(); -#if DEBUG_SHUFFLE_BUFFER +#if DEBUG_SHUFFLE_BUFFER() // guarantee allocation of a buffer, even with a requested size of 0, so we have a place to put our overrun barriers if ((p_buffer_size > shuffle_buf_capacity_) || !shuffle_buffer_) #else @@ -4661,7 +5236,7 @@ slim_popsize_t *Species::BorrowShuffleBuffer(slim_popsize_t p_buffer_size) if (shuffle_buffer_) free(shuffle_buffer_); shuffle_buf_capacity_ = p_buffer_size * 2; // double capacity so we reallocate less often -#if DEBUG_SHUFFLE_BUFFER +#if DEBUG_SHUFFLE_BUFFER() // room for an extra value at the start and end shuffle_buffer_ = (slim_popsize_t *)malloc((shuffle_buf_capacity_ + 2) * sizeof(slim_popsize_t)); #else @@ -4673,7 +5248,7 @@ slim_popsize_t *Species::BorrowShuffleBuffer(slim_popsize_t p_buffer_size) EIDOS_TERMINATION << "ERROR (Species::BorrowShuffleBuffer): allocation failed (requested size " << p_buffer_size << " entries, allocation size " << (shuffle_buf_capacity_ * sizeof(slim_popsize_t)) << " bytes); you may need to raise the memory limit for SLiM." << EidosTerminate(); } -#if DEBUG_SHUFFLE_BUFFER +#if DEBUG_SHUFFLE_BUFFER() // put flag values in to detect an overrun slim_popsize_t *buffer_contents = shuffle_buffer_ + 1; @@ -4716,7 +5291,7 @@ slim_popsize_t *Species::BorrowShuffleBuffer(slim_popsize_t p_buffer_size) } } -#if DEBUG_SHUFFLE_BUFFER +#if DEBUG_SHUFFLE_BUFFER() // check for correct setup of flag values; entries 1:shuffle_buf_size_ are used if (shuffle_buffer_[0] != (slim_popsize_t)0xDEADD00D) EIDOS_TERMINATION << "ERROR (Species::BorrowShuffleBuffer): (internal error) shuffle buffer overrun at start." << EidosTerminate(); @@ -4733,7 +5308,7 @@ void Species::ReturnShuffleBuffer(void) if (!shuffle_buf_borrowed_) EIDOS_TERMINATION << "ERROR (Species::ReturnShuffleBuffer): (internal error) shuffle buffer was not borrowed." << EidosTerminate(); -#if DEBUG_SHUFFLE_BUFFER +#if DEBUG_SHUFFLE_BUFFER() // check for correct setup of flag values; entries 1:shuffle_buf_size_ are used if (shuffle_buffer_[0] != (slim_popsize_t)0xDEADD00D) EIDOS_TERMINATION << "ERROR (Species::ReturnShuffleBuffer): (internal error) shuffle buffer overrun at start." << EidosTerminate(); @@ -4746,7 +5321,7 @@ void Species::ReturnShuffleBuffer(void) #if (SLIMPROFILING == 1) // PROFILING -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() void Species::CollectMutationProfileInfo(void) { // maintain our history of the nonneutral regime @@ -5394,10 +5969,10 @@ void Species::SimplifyAllTreeSequences(void) // I think; otherwise we could just throw the remembered nodes and extant individuals into `samples` // with no lookup table complication. { -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map remembered_nodes_lookup; //typedef robin_hood::pair MAP_PAIR; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map remembered_nodes_lookup; //typedef std::pair MAP_PAIR; #endif @@ -8579,9 +9154,9 @@ void Species::__CheckPopulationMetadata(TreeSeqInfo &p_treeseq) } // We need a reverse hash to construct the remapped population table -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() typedef robin_hood::unordered_flat_map SUBPOP_REMAP_REVERSE_HASH; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() typedef std::unordered_map SUBPOP_REMAP_REVERSE_HASH; #endif diff --git a/core/species.h b/core/species.h index 884bb52e..23047217 100644 --- a/core/species.h +++ b/core/species.h @@ -193,10 +193,10 @@ class Species : public EidosDictionaryUnretained // for multiple chromosomes, we now have a vector of pointers to Chromosome objects, // as well as hash tables for quick lookup by id and symbol -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() typedef robin_hood::unordered_flat_map CHROMOSOME_ID_HASH; typedef robin_hood::unordered_flat_map CHROMOSOME_SYMBOL_HASH; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() typedef std::unordered_map CHROMOSOME_ID_HASH; typedef std::unordered_map CHROMOSOME_SYMBOL_HASH; #endif @@ -219,10 +219,10 @@ class Species : public EidosDictionaryUnretained // for multiple traits, we now have a vector of pointers to Trait objects, as well as hash tables for quick // lookup by name and by string ID; the latter is to make using trait names as properties on Individual fast -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() typedef robin_hood::unordered_flat_map TRAIT_NAME_HASH; typedef robin_hood::unordered_flat_map TRAIT_STRID_HASH; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() typedef std::unordered_map TRAIT_NAME_HASH; typedef std::unordered_map TRAIT_STRID_HASH; #endif @@ -238,9 +238,9 @@ class Species : public EidosDictionaryUnretained bool sex_enabled_ = false; // true if sex is tracked for individuals; if false, all individuals are hermaphroditic // private initialization methods -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() typedef robin_hood::unordered_flat_map SUBPOP_REMAP_HASH; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() typedef std::unordered_map SUBPOP_REMAP_HASH; #endif @@ -336,9 +336,9 @@ class Species : public EidosDictionaryUnretained // actually be shared by multiple haplosomes in different chromosomes //Individual *current_new_individual_; -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() typedef robin_hood::unordered_flat_map INDIVIDUALS_HASH; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() typedef std::unordered_map INDIVIDUALS_HASH; #endif INDIVIDUALS_HASH tabled_individuals_hash_; // look up individuals table row numbers from pedigree IDs @@ -403,11 +403,15 @@ class Species : public EidosDictionaryUnretained // this switches to a less optimized case when evolving in WF models, if a type 's' DES could be present, since that can open up various cans of worms bool type_s_DESs_present_ = false; // optimization flag - // this counter is incremented when a selection coefficient is changed on any mutation object in the simulation. This is used as a signal to mutation runs that their - // cache of non-neutral mutations is invalid (because their counter is not equal to this counter). The caches will be re-validated the next time they are used. Other - // code can also increment this counter in order to trigger a re-validation of all non-neutral mutation caches; it is a general-purpose mechanism. - int32_t nonneutral_change_counter_ = 0; - int32_t last_nonneutral_regime_ = 0; // see mutation_run.h; 1 = no mutationEffect() callbacks, 2 = only constant-effect neutral callbacks, 3 = arbitrary callbacks +#if SLIM_USE_NONNEUTRAL_CACHES() + // this flag is set whenever a change invalidates all non-neutral caches in the species; the caches will be re-validated the next time they are used + // note that there are finer-grained invalidation flags elsewhere that should be used if a total invalidation is not actually necessary, to save work + bool all_nonneutral_caches_invalid_ = true; +#endif // SLIM_USE_NONNEUTRAL_CACHES() + + // the current trait calculation regime, under which the current nonneutral caches were constructed; see mutation_run.h and Species::ValidateNonNeutralCaches() + // note that this is only the top-level strategy for building the nonneutral caches; flags in MutationType and Mutation also affect the process + TraitCalculationRegime current_trait_calculation_regime_ = TraitCalculationRegime::kUndefined; // state about what symbols/names/identifiers have been used or are being used // used_subpop_ids_ has every subpop id ever used, even if no longer in use, with the *last* name used for that subpop @@ -421,10 +425,10 @@ class Species : public EidosDictionaryUnretained SLiMMemoryUsage_Species profile_last_memory_usage_Species; SLiMMemoryUsage_Species profile_total_memory_usage_Species; -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() std::vector profile_nonneutral_regime_history_; // a record of the nonneutral regime used in each cycle int64_t profile_max_mutation_index_; // the largest mutation index seen over the course of the profile -#endif // SLIM_USE_NONNEUTRAL_CACHES +#endif // SLIM_PROFILE_NONNEUTRAL_CACHES() #endif // (SLIMPROFILING == 1) Species(const Species&) = delete; // no copying @@ -443,7 +447,9 @@ class Species : public EidosDictionaryUnretained // is known, and has already been factored in to SLiM optimization settings. if (!p_mut->is_neutral_for_all_traits_) { - p_mut->mutation_type_ptr_->species_.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags +#if SLIM_USE_NONNEUTRAL_CACHES() + p_mut->mutation_type_ptr_->species_.all_nonneutral_caches_invalid_ = true; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags +#endif } else { @@ -463,9 +469,24 @@ class Species : public EidosDictionaryUnretained // This needs to be done in the neutral case too; for example, a non-neutral mutation might // have been changed into a neutral mutation, which invalidates our non-neutral caches - p_mut->mutation_type_ptr_->species_.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags +#if SLIM_USE_NONNEUTRAL_CACHES() + p_mut->mutation_type_ptr_->species_.all_nonneutral_caches_invalid_ = true; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags +#endif } + void PrepareForTraitCalculations(std::vector &mutationEffect_callbacks); + bool _CallbackMakesMutationTypeNeutral(SLiMEidosBlock *mutationEffect_callback, MutationType *&mut_type_ptr_ref); + bool _CallbackMakesTraitNeutral(SLiMEidosBlock *mutationEffect_callback, Trait *&trait_ptr_ref); + +#if SLIM_USE_NONNEUTRAL_CACHES() + // Validates the MutationRun non-neutral caches across the species. This must be called immediately before nonneutral cache use, every time. + // Note that it does not call mutationEffect() callbacks; it just needs to examine them to determine the status of each MutationType. + void ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calculation_regime); + + template + int64_t _ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr); +#endif + // Chromosome configuration and access inline __attribute__((always_inline)) const std::vector &Chromosomes(void) { return chromosomes_; } inline __attribute__((always_inline)) const std::vector &ChromosomesForHaplosomeIndices(void) { return chromosome_for_haplosome_index_; } @@ -544,7 +565,7 @@ class Species : public EidosDictionaryUnretained #if (SLIMPROFILING == 1) // PROFILING -#if SLIM_USE_NONNEUTRAL_CACHES +#if SLIM_PROFILE_NONNEUTRAL_CACHES() void CollectMutationProfileInfo(void); #endif #endif diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 86b4665e..636b99bc 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -3147,8 +3147,8 @@ EidosValue_SP Species::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_metho } } -#if DEBUG_TRAIT_DEMAND - std::cout << "# " << community_.Tick() << " --- demandPhenotype(): for traits {"; +#if DEBUG_TRAIT_DEMAND() + std::cout << "# " << community_.Tick() << " ====== demandPhenotype(): for traits {"; for (slim_trait_index_t trait_index : trait_indices) std::cout << " " << Traits()[trait_index]->Name(); std::cout << " } in subpops {"; @@ -3157,11 +3157,12 @@ EidosValue_SP Species::ExecuteMethod_demandPhenotype(EidosGlobalStringID p_metho std::cout << " }, forceRecalc == " << (forceRecalc ? "T" : "F") << std::endl; #endif - // validate non-neutral caches and independent-dominance precalculated values - // FIXME MULTITRAIT: VALIDATE NON-NEUTRAL CACHES HERE + // prepare for trait calculations, such as by validating non-neutral caches and independent-dominance precalculated values + std::vector mutationEffect_callbacks = CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); + + PrepareForTraitCalculations(mutationEffect_callbacks); // call DemandPhenotype_SUBPOP() to express the demand, one subpop at a time - std::vector mutationEffect_callbacks = Species::CallbackBlocksMatching(community_.Tick(), SLiMEidosBlockType::SLiMEidosMutationEffectCallback, -1, -1, -1, -1, -1, /* p_active_only */ true); std::vector subpop_mutationEffect_callbacks; for (Subpopulation *subpop : subpops_to_demand) @@ -3264,10 +3265,10 @@ EidosValue_SP Species::ExecuteMethod_individualsWithPedigreeIDs(EidosGlobalStrin // for larger problem sizes, we speed up lookups by building a hash table first, changing from O(N*M) to O(N) // we could get even more fancy and cache this hash table to speed up successive calls within one cycle, // but since the hash table is specific to the set of subpops we're searching, that would get a bit hairy... -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map fromIDToIndividual; //typedef robin_hood::pair MAP_PAIR; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map fromIDToIndividual; //typedef std::pair MAP_PAIR; #endif @@ -3728,7 +3729,7 @@ EidosValue_SP Species::ExecuteMethod_outputFixedMutations(EidosGlobalStringID p_ std::ostream &out = *(has_file ? dynamic_cast(&outfile) : dynamic_cast(&output_stream)); -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() // This method can burn a huge amount of memory and get us killed, if we have a maximum memory usage. It's nice to // try to check for that and terminate with a proper error message, to help the user diagnose the problem. int mem_check_counter = 0, mem_check_mod = 100; @@ -3760,7 +3761,7 @@ EidosValue_SP Species::ExecuteMethod_outputFixedMutations(EidosGlobalStringID p_ else subs[i]->PrintForSLiMOutput(out); -#if DO_MEMORY_CHECKS +#if DO_MEMORY_CHECKS() if (eidos_do_memory_checks) { mem_check_counter++; diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 75c4e81e..26850ddc 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -612,7 +612,7 @@ void Subpopulation::CheckIndividualIntegrity(void) if (population_.child_generation_valid_) { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // When the child generation is valid, all parental haplosomes should have null mutrun pointers [OBSOLETE: so mutrun refcounts are correct] for (int mutrun_index = 0; mutrun_index < haplosome1->mutrun_count_; ++mutrun_index) if (haplosome1->mutruns_[mutrun_index] != nullptr) @@ -621,7 +621,7 @@ void Subpopulation::CheckIndividualIntegrity(void) } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // When the parental generation is valid, all parental haplosomes should have non-null mutrun pointers for (int mutrun_index = 0; mutrun_index < haplosome1->mutrun_count_; ++mutrun_index) if (haplosome1->mutruns_[mutrun_index] == nullptr) @@ -741,7 +741,7 @@ void Subpopulation::CheckIndividualIntegrity(void) if (population_.child_generation_valid_) { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // When the child generation is valid, all parental haplosomes should have null mutrun pointers [OBSOLETE: so mutrun refcounts are correct] for (int mutrun_index = 0; mutrun_index < haplosome2->mutrun_count_; ++mutrun_index) if (haplosome2->mutruns_[mutrun_index] != nullptr) @@ -750,7 +750,7 @@ void Subpopulation::CheckIndividualIntegrity(void) } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // When the parental generation is valid, all parental haplosomes should have non-null mutrun pointers for (int mutrun_index = 0; mutrun_index < haplosome2->mutrun_count_; ++mutrun_index) if (haplosome2->mutruns_[mutrun_index] == nullptr) @@ -854,7 +854,7 @@ void Subpopulation::CheckIndividualIntegrity(void) EIDOS_TERMINATION << "ERROR (Subpopulation::CheckIndividualIntegrity): (internal error) haplosome1 of individual has the wrong mutrun count/length." << EidosTerminate(); // do not check haplosomes in the child generation; they are conceptually cleared to - // nullptr (but can actually even contain garbage, unless SLIM_CLEAR_HAPLOSOMES is set + // nullptr (but can actually even contain garbage, unless SLIM_CLEAR_HAPLOSOMES() is set } if (((haplosome1->mutrun_count_ == 0) && ((haplosome1->mutrun_length_ != 0) || (haplosome1->mutruns_ != nullptr))) || @@ -908,7 +908,7 @@ void Subpopulation::CheckIndividualIntegrity(void) if (!population_.child_generation_valid_) { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // When the parental generation is valid, all child haplosomes should have null mutrun pointers [OBSOLETE: so mutrun refcounts are correct] for (int mutrun_index = 0; mutrun_index < haplosome1->mutrun_count_; ++mutrun_index) if (haplosome1->mutruns_[mutrun_index] != nullptr) @@ -917,7 +917,7 @@ void Subpopulation::CheckIndividualIntegrity(void) } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // When the child generation is valid, all child haplosomes should have non-null mutrun pointers for (int mutrun_index = 0; mutrun_index < haplosome1->mutrun_count_; ++mutrun_index) if (haplosome1->mutruns_[mutrun_index] == nullptr) @@ -962,7 +962,7 @@ void Subpopulation::CheckIndividualIntegrity(void) EIDOS_TERMINATION << "ERROR (Subpopulation::CheckIndividualIntegrity): (internal error) haplosome2 of individual has the wrong mutrun count/length." << EidosTerminate(); // do not check haplosomes in the child generation; they are conceptually cleared to - // nullptr (but can actually even contain garbage, unless SLIM_CLEAR_HAPLOSOMES is set + // nullptr (but can actually even contain garbage, unless SLIM_CLEAR_HAPLOSOMES() is set } if (((haplosome2->mutrun_count_ == 0) && ((haplosome2->mutrun_length_ != 0) || (haplosome2->mutruns_ != nullptr))) || @@ -1020,7 +1020,7 @@ void Subpopulation::CheckIndividualIntegrity(void) if (!population_.child_generation_valid_) { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // When the parental generation is valid, all child haplosomes should have null mutrun pointers [OBSOLETE: so mutrun refcounts are correct] for (int mutrun_index = 0; mutrun_index < haplosome2->mutrun_count_; ++mutrun_index) if (haplosome2->mutruns_[mutrun_index] != nullptr) @@ -1029,7 +1029,7 @@ void Subpopulation::CheckIndividualIntegrity(void) } else { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // When the child generation is valid, all child haplosomes should have non-null mutrun pointers for (int mutrun_index = 0; mutrun_index < haplosome2->mutrun_count_; ++mutrun_index) if (haplosome2->mutruns_[mutrun_index] == nullptr) @@ -1104,7 +1104,7 @@ void Subpopulation::CheckIndividualIntegrity(void) if (haplosome->IsNull()) EIDOS_TERMINATION << "ERROR (Subpopulation::CheckIndividualIntegrity): (internal error) null haplosome in the nonnull haplosome junkyard." << EidosTerminate(); -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() haplosome->check_cleared_to_nullptr(); #endif } @@ -1114,7 +1114,7 @@ void Subpopulation::CheckIndividualIntegrity(void) if (!haplosome->IsNull()) EIDOS_TERMINATION << "ERROR (Subpopulation::CheckIndividualIntegrity): (internal error) nonnull haplosome in the null haplosome junkyard." << EidosTerminate(); -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() haplosome->check_cleared_to_nullptr(); #endif } @@ -1319,39 +1319,6 @@ slim_refcount_t Subpopulation::NullHaplosomeCount(void) return null_haplosome_count; } -#if (defined(_OPENMP) && SLIM_USE_NONNEUTRAL_CACHES) -void Subpopulation::FixNonNeutralCaches_OMP(void) -{ - // This is used in the parallel fitness evaluation case to fix caches up front - // This is task-based; note the top-level parallel is *not* a parallel for loop! -#pragma omp parallel default(none) - { -#pragma omp single - { - int32_t nonneutral_change_counter = species_.nonneutral_change_counter_; - int32_t nonneutral_regime = species_.last_nonneutral_regime_; -#error FIXME this *2 is now based on an incorrect assumption of simple diploidy - slim_popsize_t haplosomeCount = parent_subpop_size_ * 2; - - for (slim_popsize_t haplosome_index = 0; haplosome_index < haplosomeCount; haplosome_index++) - { - Haplosome *haplosome = parent_haplosomes_[haplosome_index]; - const int32_t mutrun_count = haplosome->mutrun_count_; - - for (int run_index = 0; run_index < mutrun_count; ++run_index) - { - const MutationRun *mutrun = haplosome->mutruns_[run_index]; - - // This will start a new task if the mutrun needs to validate - // its nonneutral cache. It avoids doing so more than once. - mutrun->validate_nonneutral_cache(nonneutral_change_counter, nonneutral_regime); - } - } - } - } -} -#endif - // Population::RecalculateFitness() figures out which callbacks are relevant for each subpopulation, and which // traits need to be evaluated in order to calculate fitness (only with a direct fitness effect). It then // calls UpdateFitness() on each subpopulation. This method expresses demand for the traits in question, and @@ -1388,7 +1355,7 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio { // we know this subpopulation has effectively constant fitness; we therefore don't express demand for // any traits, which means trait may keep NAN values even if the traits have a direct fitness effect -#if DEBUG_TRAIT_DEMAND +#if DEBUG_TRAIT_DEMAND() std::cout << "# " << community_.Tick() << " --- UpdateFitness() determined constant fitness of " << constant_fitness_value << std::endl; #endif @@ -1429,7 +1396,7 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio // demand phenotypes for all the relevant traits if (p_direct_effect_trait_indices.size()) { -#if DEBUG_TRAIT_DEMAND +#if DEBUG_TRAIT_DEMAND() std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding traits {"; for (slim_trait_index_t trait_index : p_direct_effect_trait_indices) std::cout << " " << species_.Traits()[trait_index]->Name(); @@ -1443,7 +1410,7 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio } else { -#if DEBUG_TRAIT_DEMAND +#if DEBUG_TRAIT_DEMAND() std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding NO traits in subpop p" << subpopulation_id_ << std::endl; #endif } @@ -1827,10 +1794,7 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati if ((callback_mutation_type_id == -1) || (callback_mutation_type_id == mutation_type_id)) { -#ifndef DEBUG_POINTS_ENABLED -#error "DEBUG_POINTS_ENABLED is not defined; include eidos_globals.h" -#endif -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -2017,7 +1981,7 @@ slim_fitness_t Subpopulation::ApplyFitnessEffectCallbacks(std::vectorcheck_cleared_to_nullptr(); #endif if (!haplosome1->IsNull()) @@ -3222,7 +3186,7 @@ Individual *Subpopulation::GenerateIndividualEmpty(slim_popsize_t p_individual_i } if (haplosome2) { -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() haplosome2->check_cleared_to_nullptr(); #endif if (!haplosome2->IsNull()) @@ -3632,7 +3596,7 @@ bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigree // back out child state we created; this restores it to a reuseable state // FIXME we could back out the assigned pedigree ID too -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // BCH 10/15/2024: We used to need to clear here, but we no longer do. We don't even need to free // the haplosomes we made above; they will be garbage collected by FreeUnusedMutationRuns(). int haplosome_count_per_individual = species_.HaplosomeCountPerIndividual(); @@ -4001,7 +3965,7 @@ bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreei // back out child state we created; this restores it to a reuseable state // FIXME we could back out the assigned pedigree ID too -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // BCH 10/15/2024: We used to need to clear here, but we no longer do. We don't even need to free // the haplosomes we made above; they will be garbage collected by FreeUnusedMutationRuns(). int haplosome_count_per_individual = species_.HaplosomeCountPerIndividual(); @@ -4292,7 +4256,7 @@ bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreei // back out child state we created; this restores it to a reuseable state // FIXME we could back out the assigned pedigree ID too -#if SLIM_CLEAR_HAPLOSOMES +#if SLIM_CLEAR_HAPLOSOMES() // BCH 10/15/2024: We used to need to clear here, but we no longer do. We don't even need to free // the haplosomes we made above; they will be garbage collected by FreeUnusedMutationRuns(). int haplosome_count_per_individual = species_.HaplosomeCountPerIndividual(); @@ -4528,7 +4492,7 @@ void Subpopulation::ApplyReproductionCallbacks(std::vector &p_r if ((sex_specificity == IndividualSex::kUnspecified) || (sex_specificity == individual->sex_)) { -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -4739,10 +4703,7 @@ bool Subpopulation::ApplySurvivalCallbacks(std::vector &p_survi { if (survival_callback->block_active_) { -#ifndef DEBUG_POINTS_ENABLED -#error "DEBUG_POINTS_ENABLED is not defined; include eidos_globals.h" -#endif -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; diff --git a/core/subpopulation.h b/core/subpopulation.h index 8a8068d0..16a34a24 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -379,10 +379,6 @@ class Subpopulation : public EidosDictionaryUnretained void GenerateParentsToFit(slim_age_t p_initial_age, double p_sex_ratio, bool p_allow_zero_size, bool p_require_both_sexes, bool p_record_in_treeseq, bool p_haploid, float p_mean_parent_age); // given the set subpop size and requested sex ratio, make new haplosomes and individuals to fit void CheckIndividualIntegrity(void); -#if (defined(_OPENMP) && SLIM_USE_NONNEUTRAL_CACHES) - void FixNonNeutralCaches_OMP(void); -#endif - // this is called by Population::RecalculateFitness(); first it expresses demand for traits that have direct fitness effects, then it recalculates fitness values void UpdateFitness(std::vector &p_subpop_mutationEffect_callbacks, std::vector &p_subpop_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation); diff --git a/core/trait.h b/core/trait.h index 690d3327..a8681854 100644 --- a/core/trait.h +++ b/core/trait.h @@ -81,11 +81,27 @@ class Trait : public EidosDictionaryRetained // OPTIMIZATION FLAGS // this is set to false if any mutation has a non-neutral effect on this trait - bool trait_all_neutral_mutations_ = true; + mutable bool trait_all_neutral_mutations_ = true; // this is set to false if any mutation has a non-neutral effect on this trait that // is not designated as "independent dominance"; neutral effects don't matter - bool trait_all_mutations_independent_dominance_ = true; + mutable bool trait_all_mutations_independent_dominance_ = true; + + // Optimization flags set up by Species::PrepareForTraitCalculations() and valid only subsequent to that call. + + // If set, it indicates that the trait is currently completely neutral, including callbacks – either because + // because trait_all_neutral_mutations_ is set and the trait cannot be influenced by any callbacks in the + // current subpopulation / tick, or because an active callback actually sets this trait to be neutral in + // this subpopulation / tick. Traits for which this flag is set can be safely elided from trait calculations + // except for the baseline offset and individual offset. + mutable bool is_pure_neutral_now; + + // If set, subject_to_mutationEffect_callback_ indicates that the trait is influenced by a mutationEffect + // callback in at least one subpop. Traits with this flag set are subject to a callback, and so the effect + // effect of mutations on that trait cannot be consulted reliably; the callback needs to be considered. + mutable bool subject_to_mutationEffect_callback_; + + mutable bool subject_to_non_neutral_callback_; Trait(const Trait&) = delete; // no copying diff --git a/eidos/eidos_class_Dictionary.h b/eidos/eidos_class_Dictionary.h index d1495715..fb211309 100644 --- a/eidos/eidos_class_Dictionary.h +++ b/eidos/eidos_class_Dictionary.h @@ -25,18 +25,18 @@ #include "eidos_class_Object.h" #include "json_fwd.hpp" -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() #include "robin_hood.h" typedef robin_hood::unordered_flat_map EidosDictionaryHashTable_StringKeys; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() #include typedef std::unordered_map EidosDictionaryHashTable_StringKeys; #endif -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() #include "robin_hood.h" typedef robin_hood::unordered_flat_map EidosDictionaryHashTable_IntegerKeys; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() #include typedef std::unordered_map EidosDictionaryHashTable_IntegerKeys; #endif diff --git a/eidos/eidos_functions_other.cpp b/eidos/eidos_functions_other.cpp index 20d14e3a..e4f0400d 100644 --- a/eidos/eidos_functions_other.cpp +++ b/eidos/eidos_functions_other.cpp @@ -194,7 +194,7 @@ EidosValue_SP Eidos_ExecuteFunction_date(__attribute__((unused)) const std::vect // (string$)debugIndent(void) EidosValue_SP Eidos_ExecuteFunction_debugIndent(__attribute__((unused)) const std::vector &p_arguments, __attribute__((unused)) EidosInterpreter &p_interpreter) { -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(EidosDebugPointIndent::Indent())); #else return gStaticEidosValue_StringEmpty; diff --git a/eidos/eidos_functions_values.cpp b/eidos/eidos_functions_values.cpp index 7e3ef992..c63e59be 100644 --- a/eidos/eidos_functions_values.cpp +++ b/eidos/eidos_functions_values.cpp @@ -29,7 +29,7 @@ #include #include "eidos_globals.h" -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() #include "robin_hood.h" #endif @@ -2056,10 +2056,10 @@ EidosValue_SP Eidos_ExecuteFunction_match(const std::vector &p_ar if ((x_count >= 500) && (table_count >= 5)) // a guess based on timing data; will be platform-dependent and dataset-dependent { // use a hash table to speed up lookups from O(N) to O(1) -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map fromValueToIndex; //typedef robin_hood::pair MAP_PAIR; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map fromValueToIndex; //typedef std::pair MAP_PAIR; #endif @@ -2104,10 +2104,10 @@ EidosValue_SP Eidos_ExecuteFunction_match(const std::vector &p_ar // use a hash table to speed up lookups from O(N) to O(1) // we have to use a custom comparator so that NAN==NAN is true, so that NAN gets matched correctly auto equal = [](const double& l, const double& r) { if (std::isnan(l) && std::isnan(r)) return true; return l == r; }; -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map, decltype(equal)> fromValueToIndex(0, robin_hood::hash{}, equal); //typedef robin_hood::pair MAP_PAIR; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map, decltype(equal)> fromValueToIndex(0, std::hash{}, equal); //typedef std::pair MAP_PAIR; #endif @@ -2154,10 +2154,10 @@ EidosValue_SP Eidos_ExecuteFunction_match(const std::vector &p_ar if ((x_count >= 500) && (table_count >= 5)) // a guess based on timing data; will be platform-dependent and dataset-dependent { // use a hash table to speed up lookups from O(N) to O(1) -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map fromValueToIndex; //typedef robin_hood::pair MAP_PAIR; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map fromValueToIndex; //typedef std::pair MAP_PAIR; #endif @@ -2201,10 +2201,10 @@ EidosValue_SP Eidos_ExecuteFunction_match(const std::vector &p_ar if ((x_count >= 500) && (table_count >= 5)) // a guess based on timing data; will be platform-dependent and dataset-dependent { // use a hash table to speed up lookups from O(N) to O(1) -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() robin_hood::unordered_flat_map fromValueToIndex; //typedef robin_hood::pair MAP_PAIR; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() std::unordered_map fromValueToIndex; //typedef std::pair MAP_PAIR; #endif diff --git a/eidos/eidos_globals.cpp b/eidos/eidos_globals.cpp index 8dff8c89..77da8390 100644 --- a/eidos/eidos_globals.cpp +++ b/eidos/eidos_globals.cpp @@ -103,7 +103,7 @@ EidosSymbolTable *gEidosConstantsSymbolTable = nullptr; int gEidosFloatOutputPrecision = 6; -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() int gEidosDebugIndent = 0; #endif @@ -2854,7 +2854,7 @@ int Eidos_mkstemps_directory(char *p_pattern, int p_suffix_len) return -1; } -#if EIDOS_BUFFER_ZIP_APPENDS +#if EIDOS_BUFFER_ZIP_APPENDS() // This contains all unflushed append data for zip files written by writeFile(); see Eidos_FlushFiles() below std::unordered_map gEidosBufferedZipAppendData; @@ -2906,7 +2906,7 @@ void Eidos_FlushFile(const std::string &p_file_path) { THREAD_SAFETY_IN_ACTIVE_PARALLEL("Eidos_FlushFile(): filesystem write"); -#if EIDOS_BUFFER_ZIP_APPENDS +#if EIDOS_BUFFER_ZIP_APPENDS() auto buffer_iter = gEidosBufferedZipAppendData.find(p_file_path); if (buffer_iter != gEidosBufferedZipAppendData.end()) @@ -2927,7 +2927,7 @@ bool Eidos_FlushFiles(void) { THREAD_SAFETY_IN_ACTIVE_PARALLEL("Eidos_FlushFiles(): filesystem write"); -#if EIDOS_BUFFER_ZIP_APPENDS +#if EIDOS_BUFFER_ZIP_APPENDS() // Write out buffered data in gEidosBufferedZipAppendData to the appropriate files, using zlib's gzip append mode bool success = true; @@ -2962,10 +2962,10 @@ void Eidos_WriteToFile(const std::string &p_file_path, const std::vector &p_cons // Change this define to 1 to enable Robin Hood Hashing, or change it to 0 to disable it // Note that you have a choice of robin_hood::unordered_node_map or robin_hood::unordered_flat_map // With robin_hood::unordered_flat_map, references to elements are not stable, but it is probably a bit faster -// STD_UNORDERED_MAP_HASHING is the reverse flag; this just makes it easy to get an error message if this header +// STD_UNORDERED_MAP_HASHING() is the reverse flag; this just makes it easy to get an error message if this header // is not included, following a standard usage pattern, since then neither of these defines will exist. -#define EIDOS_ROBIN_HOOD_HASHING 1 +// +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define EIDOS_ROBIN_HOOD_HASHING() 1 -#if EIDOS_ROBIN_HOOD_HASHING -#define STD_UNORDERED_MAP_HASHING 0 +#if EIDOS_ROBIN_HOOD_HASHING() +#define STD_UNORDERED_MAP_HASHING() 0 #else -#define STD_UNORDERED_MAP_HASHING 1 +#define STD_UNORDERED_MAP_HASHING() 1 #endif @@ -233,22 +235,26 @@ extern bool eidos_do_memory_checks; // To run slim under Valgrind, setting this flag to 1 is also recommended as it will enable some thunks that will // keep Valgrind from getting confused. Use a DEBUG build so it is symbolicated (-g) and minimally optimized (-Og), // or add those flags to CMAKE_C_FLAGS_RELEASE and CMAKE_CXX_FLAGS_RELEASE in CMakeLists.txt. -#define SLIM_LEAK_CHECKING 0 +// +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define SLIM_LEAK_CHECKING() 0 -#if SLIM_LEAK_CHECKING -#warning SLIM_LEAK_CHECKING enabled! +#if SLIM_LEAK_CHECKING() +#warning SLIM_LEAK_CHECKING() enabled! #endif // Enabling "debug points" in SLiMgui. These are only enabled under SLiMgui (i.e., QtSLiM), and should always be enabled // in that scenario, for end users. However, I've put a define here to control them for my own debugging purposes. +// +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ #ifdef SLIMGUI -#define DEBUG_POINTS_ENABLED 1 +#define DEBUG_POINTS_ENABLED() 1 extern int gEidosDebugIndent; #else -#define DEBUG_POINTS_ENABLED 0 +#define DEBUG_POINTS_ENABLED() 0 #endif -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // A simple class for RAII-based debug point indentation; saves some hassle with exceptions, etc. class EidosDebugPointIndent { @@ -628,9 +634,11 @@ int Eidos_mkstemps(char *p_pattern, int p_suffix_len); int Eidos_mkstemps_directory(char *p_pattern, int p_suffix_len); // Writing files with support for gzip compression and buffered flushing -#define EIDOS_BUFFER_ZIP_APPENDS 1 +// +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define EIDOS_BUFFER_ZIP_APPENDS() 1 -#if EIDOS_BUFFER_ZIP_APPENDS // implementation details for Eidos_FlushFiles(); for internal use only +#if EIDOS_BUFFER_ZIP_APPENDS() // implementation details for Eidos_FlushFiles(); for internal use only extern std::unordered_map gEidosBufferedZipAppendData; // canonical absolute file path -> buffered text bool _Eidos_FlushZipBuffer(const std::string &p_file_path, const std::string &p_outstring); #endif @@ -951,7 +959,8 @@ class Eidos_simple_lock #ifdef Eidos_add_overflow -#define EIDOS_HAS_OVERFLOW_BUILTINS 1 +// Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define EIDOS_HAS_OVERFLOW_BUILTINS() 1 #else @@ -960,7 +969,7 @@ class Eidos_simple_lock #define Eidos_add_overflow(a, b, c) (*(c)=(a)+(b), false) #define Eidos_sub_overflow(a, b, c) (*(c)=(a)-(b), false) #define Eidos_mul_overflow(a, b, c) (*(c)=(a)*(b), false) -#define EIDOS_HAS_OVERFLOW_BUILTINS 0 +#define EIDOS_HAS_OVERFLOW_BUILTINS() 0 #endif @@ -1073,7 +1082,7 @@ class EidosStringRegistry return EidosStringRegistry::sharedRegistry()._StringForGlobalStringID(p_string_id); } -#if SLIM_LEAK_CHECKING +#if SLIM_LEAK_CHECKING() static inline void ThunkRegistration(_EidosRegisteredString *p_registration_object) { EidosStringRegistry::sharedRegistry().gIDToString_Thunk.emplace_back(p_registration_object); diff --git a/eidos/eidos_interpreter.cpp b/eidos/eidos_interpreter.cpp index d9ca743c..64ec95ec 100644 --- a/eidos/eidos_interpreter.cpp +++ b/eidos/eidos_interpreter.cpp @@ -958,10 +958,7 @@ EidosValue_SP EidosInterpreter::Evaluate_NullStatement(const EidosASTNode *p_nod EIDOS_ENTRY_EXECUTION_LOG("Evaluate_NullStatement()"); EIDOS_ASSERT_CHILD_COUNT("EidosInterpreter::Evaluate_NullStatement", 0); -#ifndef DEBUG_POINTS_ENABLED -#error "DEBUG_POINTS_ENABLED is not defined; include eidos_globals.h" -#endif -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point if (debug_points_ && debug_points_->set.size() && (p_node->token_->token_line_ != -1) && (debug_points_->set.find(p_node->token_->token_line_) != debug_points_->set.end())) @@ -1433,7 +1430,7 @@ void EidosInterpreter::_CreateArgumentList(const EidosASTNode *p_node, const Eid EidosValue_SP EidosInterpreter::DispatchUserDefinedFunction(const EidosFunctionSignature &p_function_signature, const std::vector &p_arguments) { -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -1632,7 +1629,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Call(const EidosASTNode *p_node) // Argument processing std::vector *argument_buffer = _ProcessArgumentList(p_node, function_signature); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -1682,7 +1679,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Call(const EidosASTNode *p_node) EIDOS_TERMINATION << "ERROR (EidosInterpreter::Evaluate_Call): (internal error) function " << *function_name << " returned nullptr." << EidosTerminate(call_identifier_token); #endif -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point if (debug_points_ && debug_points_->set.size() && (call_identifier_token->token_line_ != -1) && (debug_points_->set.find(call_identifier_token->token_line_) != debug_points_->set.end())) @@ -1758,7 +1755,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Call(const EidosASTNode *p_node) // Argument processing std::vector *argument_buffer = _ProcessArgumentList(p_node, method_signature); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -1791,7 +1788,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Call(const EidosASTNode *p_node) _DeprocessArgumentList(p_node, argument_buffer); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point if (debug_points_ && debug_points_->set.size() && (call_identifier_token->token_line_ != -1) && (debug_points_->set.find(call_identifier_token->token_line_) != debug_points_->set.end())) @@ -4182,7 +4179,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Assign(const EidosASTNode *p_node) EidosASTNode *lvalue_node = p_node->children_[0]; EidosValue_SP rvalue = FastEvaluateNode(p_node->children_[1]); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -5466,7 +5463,7 @@ EidosValue_SP EidosInterpreter::Evaluate_If(const EidosASTNode *p_node) EidosASTNode *condition_node = p_node->children_[0]; EidosValue_SP condition_result = FastEvaluateNode(condition_node); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -5582,7 +5579,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Do(const EidosASTNode *p_node) EidosToken *operator_token = p_node->token_; EidosValue_SP result_SP; -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -5644,7 +5641,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Do(const EidosASTNode *p_node) EidosASTNode *condition_node = p_node->children_[1]; EidosValue_SP condition_result = FastEvaluateNode(condition_node); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point if (debug_points_ && debug_points_->set.size() && (operator_token->token_line_ != -1) && (debug_points_->set.find(operator_token->token_line_) != debug_points_->set.end()) && @@ -5716,7 +5713,7 @@ EidosValue_SP EidosInterpreter::Evaluate_While(const EidosASTNode *p_node) EidosASTNode *condition_node = p_node->children_[0]; EidosValue_SP condition_result = FastEvaluateNode(condition_node); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; @@ -6013,7 +6010,7 @@ EidosValue_SP EidosInterpreter::Evaluate_For(const EidosASTNode *p_node) { for (int range_index = 0; range_index < iteration_count; ++range_index) { -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; bool log_debug_point = false; @@ -6062,7 +6059,7 @@ EidosValue_SP EidosInterpreter::Evaluate_For(const EidosASTNode *p_node) *int_data = iterator_int_value; } -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point if (log_debug_point) { @@ -6161,7 +6158,7 @@ EidosValue_SP EidosInterpreter::Evaluate_For(const EidosASTNode *p_node) } } -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point if (log_debug_point) { @@ -6175,7 +6172,7 @@ EidosValue_SP EidosInterpreter::Evaluate_For(const EidosASTNode *p_node) #endif } -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point if (log_debug_point) { @@ -6268,7 +6265,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Next(const EidosASTNode *p_node) EIDOS_ENTRY_EXECUTION_LOG("Evaluate_Next()"); EIDOS_ASSERT_CHILD_COUNT("EidosInterpreter::Evaluate_Next", 0); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point if (debug_points_ && debug_points_->set.size() && (p_node->token_->token_line_ != -1) && (debug_points_->set.find(p_node->token_->token_line_) != debug_points_->set.end())) @@ -6296,7 +6293,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Break(const EidosASTNode *p_node) EIDOS_ENTRY_EXECUTION_LOG("Evaluate_Break()"); EIDOS_ASSERT_CHILD_COUNT("EidosInterpreter::Evaluate_Break", 0); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point if (debug_points_ && debug_points_->set.size() && (p_node->token_->token_line_ != -1) && (debug_points_->set.find(p_node->token_->token_line_) != debug_points_->set.end())) @@ -6340,7 +6337,7 @@ EidosValue_SP EidosInterpreter::Evaluate_Return(const EidosASTNode *p_node) else result_SP = FastEvaluateNode(p_node->children_[0]); -#if DEBUG_POINTS_ENABLED +#if DEBUG_POINTS_ENABLED() // SLiMgui debugging point EidosDebugPointIndent indenter; diff --git a/eidos/eidos_interpreter.h b/eidos/eidos_interpreter.h index 27d9fbcb..61052c04 100644 --- a/eidos/eidos_interpreter.h +++ b/eidos/eidos_interpreter.h @@ -62,13 +62,13 @@ bool TypeCheckAssignmentOfEidosValueIntoEidosValue(const EidosValue &p_base_valu // This is a bit convoluted so that we can forward-declare it in other headers even though it's a typedef of a // templated class; I couldn't figure out a better way to do it than wrapping it in a struct, ugh. #ifdef SLIMGUI -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() #include "robin_hood.h" struct EidosInterpreterDebugPointsSet_struct { robin_hood::unordered_set set; }; typedef EidosInterpreterDebugPointsSet_struct EidosInterpreterDebugPointsSet; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() #include struct EidosInterpreterDebugPointsSet_struct { std::unordered_set set; diff --git a/eidos/eidos_openmp.h b/eidos/eidos_openmp.h index a665246f..f1ab2ecd 100644 --- a/eidos/eidos_openmp.h +++ b/eidos/eidos_openmp.h @@ -40,11 +40,11 @@ on macOS, you may (several times!) get a system alert that libomp was blocked for security; after that, go to System Preferences, Security & Privacy, tab General, click "Allow Anyway", and then click "Open" back in the system panel use the -maxThreads command-line option to change the maximum number of threads from OpenMP's default - We allocate per-thread storage (for gEidosMaxThreads threads) at the global/species level for these facilities: + We allocate per-thread storage (for gEidosMaxThreads threads) at the global/species/chromosome level for these facilities: SparseVector pools; see s_freed_sparse_vectors_PERTHREAD vs. s_freed_sparse_vectors_SINGLE random number generators; see gEidos_RNG_PERTHREAD vs. gEidos_RNG_SINGLE - MutationRunContexts; see mutation_run_context_PERTHREAD vs. mutation_run_SINGLE + MutationRunContexts; see mutation_run_context_PERTHREAD vs. mutation_run_context_SINGLE_ We use #pragma omp critical to protect some places in the code, with specified names diff --git a/eidos/eidos_test.cpp b/eidos/eidos_test.cpp index 4022ae27..c927ec1a 100644 --- a/eidos/eidos_test.cpp +++ b/eidos/eidos_test.cpp @@ -293,7 +293,7 @@ int RunEidosTests(void) gEidosTestSuccessCount = 0; gEidosTestFailureCount = 0; -#if (!EIDOS_HAS_OVERFLOW_BUILTINS) +#if !EIDOS_HAS_OVERFLOW_BUILTINS() std::cout << "WARNING: This build of Eidos does not detect integer arithmetic overflows. Compiling Eidos with GCC version 5.0 or later, or Clang version 3.9 or later, is required for this feature. This means that integer addition, subtraction, or multiplication that overflows the 64-bit range of Eidos (" << INT64_MIN << " to " << INT64_MAX << ") will not be detected." << std::endl; #endif diff --git a/eidos/eidos_test_functions_math.cpp b/eidos/eidos_test_functions_math.cpp index f5767999..90856a57 100644 --- a/eidos/eidos_test_functions_math.cpp +++ b/eidos/eidos_test_functions_math.cpp @@ -208,7 +208,7 @@ void _RunFunctionMathTests_a_through_f(void) EidosAssertScriptSuccess("cumProduct(float(0));", gStaticEidosValue_Float_ZeroVec); EidosAssertScriptRaise("cumProduct(string(0));", 0, "cannot be type"); EidosAssertScriptSuccess_I("-9223372036854775807 - 1;", INT64_MIN); -#if EIDOS_HAS_OVERFLOW_BUILTINS +#if EIDOS_HAS_OVERFLOW_BUILTINS() EidosAssertScriptRaise("-9223372036854775807 - 2;", 21, "subtraction overflow"); EidosAssertScriptRaise("cumProduct(c(-922337203685477581, 10));", 0, "multiplication overflow"); EidosAssertScriptRaise("cumProduct(c(922337203685477581, 10));", 0, "multiplication overflow"); @@ -234,7 +234,7 @@ void _RunFunctionMathTests_a_through_f(void) EidosAssertScriptSuccess("cumSum(float(0));", gStaticEidosValue_Float_ZeroVec); EidosAssertScriptRaise("cumSum(string(0));", 0, "cannot be type"); EidosAssertScriptSuccess_I("-9223372036854775807 - 1;", INT64_MIN); -#if EIDOS_HAS_OVERFLOW_BUILTINS +#if EIDOS_HAS_OVERFLOW_BUILTINS() EidosAssertScriptRaise("-9223372036854775807 - 2;", 21, "subtraction overflow"); EidosAssertScriptRaise("cumSum(c(-9223372036854775807, -1, -1));", 0, "addition overflow"); EidosAssertScriptRaise("cumSum(c(9223372036854775807, 1, 1));", 0, "addition overflow"); @@ -491,7 +491,7 @@ void _RunFunctionMathTests_g_through_r(void) EidosAssertScriptSuccess_I("product(5);", 5); EidosAssertScriptSuccess_I("product(-5);", -5); EidosAssertScriptSuccess_I("product(c(-2, 7, -18, 12));", 3024); -#if EIDOS_HAS_OVERFLOW_BUILTINS +#if EIDOS_HAS_OVERFLOW_BUILTINS() EidosAssertScriptSuccess_F("product(c(200000000, 3000000000000, 1000));", 6e23); #endif EidosAssertScriptSuccess_F("product(5.5);", 5.5); @@ -1111,7 +1111,7 @@ void _RunFunctionMathTests_s_through_z(void) EidosAssertScriptSuccess_I("sum(-5);", -5); EidosAssertScriptSuccess_I("sum(c(-2, 7, -18, 12));", -1); EidosAssertScriptSuccess_I("sum(c(200000000, 3000000000000));", 3000200000000); -#if EIDOS_HAS_OVERFLOW_BUILTINS +#if EIDOS_HAS_OVERFLOW_BUILTINS() EidosAssertScriptSuccess_F("sum(rep(3000000000000000000, 100));", 3e20); #endif EidosAssertScriptSuccess_F("sum(5.5);", 5.5); diff --git a/eidos/eidos_test_functions_statistics.cpp b/eidos/eidos_test_functions_statistics.cpp index 46a12932..cd0fa3dd 100644 --- a/eidos/eidos_test_functions_statistics.cpp +++ b/eidos/eidos_test_functions_statistics.cpp @@ -195,7 +195,7 @@ void _RunFunctionStatisticsTests_a_through_p(void) EidosAssertScriptSuccess_F("mean(float(0));", std::numeric_limits::quiet_NaN()); // BCH 1/11/2026: changed from NULL to NAN after SLiM 5.1 EidosAssertScriptRaise("mean(string(0));", 0, "cannot be type"); EidosAssertScriptSuccess_F("mean(rep(1e18, 9));", 1e18); // stays in integer internally -#if EIDOS_HAS_OVERFLOW_BUILTINS +#if EIDOS_HAS_OVERFLOW_BUILTINS() EidosAssertScriptSuccess_F("mean(rep(1e18, 10));", 1e18); // overflows to float internally #endif EidosAssertScriptSuccess("mean(c(1.0, 5.0, NAN, 2.0));", gStaticEidosValue_FloatNAN); diff --git a/eidos/eidos_test_operators_arithmetic.cpp b/eidos/eidos_test_operators_arithmetic.cpp index 6466ec97..a2f520fd 100644 --- a/eidos/eidos_test_operators_arithmetic.cpp +++ b/eidos/eidos_test_operators_arithmetic.cpp @@ -96,7 +96,7 @@ void _RunOperatorPlusTests1(void) // operator +: raise on integer addition overflow for all code paths EidosAssertScriptSuccess_I("5e18;", 5000000000000000000LL); EidosAssertScriptRaise("1e19;", 0, "could not be represented"); -#if EIDOS_HAS_OVERFLOW_BUILTINS +#if EIDOS_HAS_OVERFLOW_BUILTINS() EidosAssertScriptRaise("5e18 + 5e18;", 5, "overflow with the binary"); EidosAssertScriptRaise("5e18 + c(0, 0, 5e18, 0);", 5, "overflow with the binary"); EidosAssertScriptRaise("c(0, 0, 5e18, 0) + 5e18;", 17, "overflow with the binary"); @@ -452,7 +452,7 @@ void _RunOperatorMinusTests(void) EidosAssertScriptSuccess_I("9223372036854775807;", INT64_MAX); EidosAssertScriptSuccess_I("-9223372036854775807 - 1;", INT64_MIN); EidosAssertScriptSuccess_I("-5e18;", -5000000000000000000LL); -#if EIDOS_HAS_OVERFLOW_BUILTINS +#if EIDOS_HAS_OVERFLOW_BUILTINS() EidosAssertScriptRaise("-(-9223372036854775807 - 1);", 0, "overflow with the unary"); EidosAssertScriptRaise("-c(-9223372036854775807 - 1, 10);", 0, "overflow with the unary"); EidosAssertScriptRaise("-5e18 - 5e18;", 6, "overflow with the binary"); @@ -524,7 +524,7 @@ void _RunOperatorMultTests(void) // operator *: raise on integer multiplication overflow for all code paths EidosAssertScriptSuccess_I("5e18;", 5000000000000000000LL); EidosAssertScriptRaise("1e19;", 0, "could not be represented"); -#if EIDOS_HAS_OVERFLOW_BUILTINS +#if EIDOS_HAS_OVERFLOW_BUILTINS() EidosAssertScriptRaise("5e18 * 2;", 5, "multiplication overflow"); EidosAssertScriptRaise("5e18 * c(0, 0, 2, 0);", 5, "multiplication overflow"); EidosAssertScriptRaise("c(0, 0, 2, 0) * 5e18;", 14, "multiplication overflow"); diff --git a/eidos/eidos_test_operators_other.cpp b/eidos/eidos_test_operators_other.cpp index 33964329..6714a24f 100644 --- a/eidos/eidos_test_operators_other.cpp +++ b/eidos/eidos_test_operators_other.cpp @@ -544,7 +544,7 @@ void _RunOperatorAssignTests(void) EidosAssertScriptSuccess_L("function (void)mod(void) { defineGlobal('x', rbind(x, 2)); } x = 1; mod(); identical(x, rbind(1, 2));", true); EidosAssertScriptSuccess_L("function (void)mod(void) { defineGlobal('x', rbind(x, 2.0)); } x = 1.0; mod(); identical(x, rbind(1.0, 2.0));", true); -#if EIDOS_HAS_OVERFLOW_BUILTINS +#if EIDOS_HAS_OVERFLOW_BUILTINS() EidosAssertScriptRaise("x = 5e18; x = x + 5e18;", 16, "overflow with the binary"); EidosAssertScriptRaise("x = c(5e18, 0); x = x + 5e18;", 22, "overflow with the binary"); EidosAssertScriptRaise("x = -5e18; x = x - 5e18;", 17, "overflow with the binary"); diff --git a/eidos/eidos_type_table.h b/eidos/eidos_type_table.h index 3bb1ef76..daaa18a9 100644 --- a/eidos/eidos_type_table.h +++ b/eidos/eidos_type_table.h @@ -36,11 +36,11 @@ #include "eidos_globals.h" -#if EIDOS_ROBIN_HOOD_HASHING +#if EIDOS_ROBIN_HOOD_HASHING() #include "robin_hood.h" typedef robin_hood::unordered_flat_map EidosTypeTableSymbols; typedef robin_hood::pair EidosTypeTableEntry; -#elif STD_UNORDERED_MAP_HASHING +#elif STD_UNORDERED_MAP_HASHING() #include typedef std::unordered_map EidosTypeTableSymbols; typedef std::pair EidosTypeTableEntry; From 5721858e81e6d61d87ae3ced584766b0483a44c2 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 17 Jan 2026 15:07:37 -0600 Subject: [PATCH 082/107] fix CI build error --- QtSLiM/QtSLiMWindow.cpp | 22 +++++++++++++--------- SLiMgui/SLiMWindowController.mm | 20 ++++++++++++-------- core/community.cpp | 2 +- core/mutation_run.h | 12 ++---------- core/slim_globals.cpp | 20 ++++++++++++-------- core/species.cpp | 32 ++++++++++++-------------------- core/species.h | 2 +- 7 files changed, 53 insertions(+), 57 deletions(-) diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index cdf255e1..6317d940 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -4190,27 +4190,31 @@ void QtSLiMWindow::displayProfileResults(void) } { - int64_t regime_tallies[3]; - int64_t regime_tallies_total = static_cast(focal_species->profile_nonneutral_regime_history_.size()); + int64_t regime_tallies[4]; + int64_t regime_tallies_total = static_cast(focal_species->profile_trait_calculation_regime_history_.size()); - for (int regime = 0; regime < 3; ++regime) + for (int regime = 0; regime < 4; ++regime) regime_tallies[regime] = 0; - for (int32_t regime : focal_species->profile_nonneutral_regime_history_) - if ((regime >= 1) && (regime <= 3)) - regime_tallies[regime - 1]++; + for (TraitCalculationRegime regime : focal_species->profile_trait_calculation_regime_history_) + { + int regime_int = (int)regime; + + if ((regime_int >= 0) && (regime_int <= 3)) + regime_tallies[regime_int]++; else regime_tallies_total--; + } - for (int regime = 0; regime < 3; ++regime) + for (int regime = 0; regime < 4; ++regime) { tc.insertText(QString("%1%").arg((regime_tallies[regime] / static_cast(regime_tallies_total)) * 100.0, 6, 'f', 2), menlo11_d); - tc.insertText(QString(" of ticks : regime %1 (%2)\n").arg(regime + 1).arg(regime == 0 ? "no mutationEffect() callbacks" : (regime == 1 ? "constant neutral mutationEffect() callbacks only" : "unpredictable mutationEffect() callbacks present")), optima13_d); + tc.insertText(QString(" of ticks : regime %1 (%2)\n").arg(regime).arg(regime == 0 ? "all mutations effectively neutral" : (regime == 1 ? "no mutationEffect() callbacks" : (regime == 2 ? "constant neutral mutationEffect() callbacks only" : "unpredictable mutationEffect() callbacks present"))), optima13_d); } tc.insertText(" \n", optima8_d); } - + tc.insertText(QString("%1").arg(focal_species->profile_max_mutation_index_), menlo11_d); tc.insertText(" maximum simultaneous mutations\n", optima13_d); diff --git a/SLiMgui/SLiMWindowController.mm b/SLiMgui/SLiMWindowController.mm index 9d02e15d..c663a3e0 100644 --- a/SLiMgui/SLiMWindowController.mm +++ b/SLiMgui/SLiMWindowController.mm @@ -1992,22 +1992,26 @@ - (void)displayProfileResults } { - int64_t regime_tallies[3]; - int64_t regime_tallies_total = (int)focal_species->profile_nonneutral_regime_history_.size(); + int64_t regime_tallies[4]; + int64_t regime_tallies_total = (int)focal_species->profile_trait_calculation_regime_history_.size(); - for (int regime = 0; regime < 3; ++regime) + for (int regime = 0; regime < 4; ++regime) regime_tallies[regime] = 0; - for (int32_t regime : focal_species->profile_nonneutral_regime_history_) - if ((regime >= 1) && (regime <= 3)) - regime_tallies[regime - 1]++; + for (TraitCalculationRegime regime : focal_species->profile_trait_calculation_regime_history_) + { + int regime_int = (int)regime; + + if ((regime_int >= 0) && (regime_int <= 3)) + regime_tallies[regime_int]++; else regime_tallies_total--; + } - for (int regime = 0; regime < 3; ++regime) + for (int regime = 0; regime < 4; ++regime) { [content eidosAppendString:[NSString stringWithFormat:@"%6.2f%%", (regime_tallies[regime] / (double)regime_tallies_total) * 100.0] attributes:menlo11_d]; - [content eidosAppendString:[NSString stringWithFormat:@" of ticks : regime %d (%@)\n", regime + 1, (regime == 0 ? @"no mutationEffect() callbacks" : (regime == 1 ? @"constant neutral mutationEffect() callbacks only" : @"unpredictable mutationEffect() callbacks present"))] attributes:optima13_d]; + [content eidosAppendString:[NSString stringWithFormat:@" of ticks : regime %d (%@)\n", regime, (regime == 0 ? @"all mutations effectively neutral" : (regime == 1 ? @"no mutationEffect() callbacks" : (regime == 2 ? @"constant neutral mutationEffect() callbacks only" : @"unpredictable mutationEffect() callbacks present")))] attributes:optima13_d]; } [content eidosAppendString:@"\n" attributes:optima8_d]; diff --git a/core/community.cpp b/core/community.cpp index 2f2e9c9a..6e138304 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -3549,7 +3549,7 @@ void Community::StartProfiling(void) // zero out mutation run metrics that are collected by CollectMutationProfileInfo() for (Species *focal_species : all_species_) { - focal_species->profile_nonneutral_regime_history_.clear(); + focal_species->profile_trait_calculation_regime_history_.clear(); focal_species->profile_max_mutation_index_ = 0; for (Chromosome *focal_chromosome : focal_species->Chromosomes()) diff --git a/core/mutation_run.h b/core/mutation_run.h index 5234e178..b4119cc7 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -96,13 +96,12 @@ typedef struct MutationRunContext { // Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ #define SLIM_USE_NONNEUTRAL_CACHES() 1 -#warning FIXME MULTITRAIT turn on profiling of non-neutral caches eventually #if SLIM_USE_NONNEUTRAL_CACHES() && (SLIMPROFILING == 1) // PROFILING: this flag should be 1 to profile the use of non-neutral caches. It's a separate flag to // separate out the profiling ramifications of non-neutral caches from all the rest of that code. // // Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ -#define SLIM_PROFILE_NONNEUTRAL_CACHES() 0 // FIXME MULTITRAIT: should be 1 +#define SLIM_PROFILE_NONNEUTRAL_CACHES() 1 #else #define SLIM_PROFILE_NONNEUTRAL_CACHES() 0 #endif // SLIM_USE_NONNEUTRAL_CACHES() && (SLIMPROFILING == 1) @@ -869,19 +868,12 @@ class MutationRun #if SLIM_PROFILE_NONNEUTRAL_CACHES() // PROFILING - // FIXME MULTITRAIT: I think maybe this gets absorbed into Species::ValidateNonNeutralCaches()? - inline __attribute__((always_inline)) void tally_nonneutral_mutations(int64_t *p_mutation_count, int64_t *p_nonneutral_count, int64_t *p_recached_count) const + inline __attribute__((always_inline)) void tally_nonneutral_mutations(int64_t *p_mutation_count, int64_t *p_nonneutral_count) const { *p_mutation_count += mutation_count_; if (nonneutral_mutations_count_ != -1) *p_nonneutral_count += nonneutral_mutations_count_; - - if (recached_run_) - { - (*p_recached_count)++; - recached_run_ = false; - } } #endif // SLIM_PROFILE_NONNEUTRAL_CACHES() diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 0bddb1c8..17b8efc1 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -2514,27 +2514,31 @@ void WriteProfileResults(std::string profile_output_path, std::string model_name } { - int64_t regime_tallies[3]; - int64_t regime_tallies_total = (int)focal_species->profile_nonneutral_regime_history_.size(); + int64_t regime_tallies[4]; + int64_t regime_tallies_total = (int)focal_species->profile_trait_calculation_regime_history_.size(); - for (int regime = 0; regime < 3; ++regime) + for (int regime = 0; regime < 4; ++regime) regime_tallies[regime] = 0; - for (int32_t regime : focal_species->profile_nonneutral_regime_history_) - if ((regime >= 1) && (regime <= 3)) - regime_tallies[regime - 1]++; + for (TraitCalculationRegime regime : focal_species->profile_trait_calculation_regime_history_) + { + int regime_int = (int)regime; + + if ((regime_int >= 0) && (regime_int <= 3)) + regime_tallies[regime_int]++; else regime_tallies_total--; + } fout << "

"; bool first_line = true; - for (int regime = 0; regime < 3; ++regime) + for (int regime = 0; regime < 4; ++regime) { if (!first_line) fout << "
\n"; snprintf(buf, 256, "%6.2f%%", (regime_tallies[regime] / (double)regime_tallies_total) * 100.0); - fout << "" << HTMLMakeSpacesNonBreaking(buf) << " of ticks : regime " << (regime + 1) << " (" << (regime == 0 ? "no mutationEffect() callbacks" : (regime == 1 ? "constant neutral mutationEffect() callbacks only" : "unpredictable mutationEffect() callbacks present")) << ")"; + fout << "" << HTMLMakeSpacesNonBreaking(buf) << " of ticks : regime " << regime << " (" << (regime == 0 ? "all mutations effectively neutral" : (regime == 1 ? "no mutationEffect() callbacks" : (regime == 2 ? "constant neutral mutationEffect() callbacks only" : "unpredictable mutationEffect() callbacks present"))) << ")"; first_line = false; } diff --git a/core/species.cpp b/core/species.cpp index 0af4dd26..caa0ab28 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -742,13 +742,6 @@ bool Species::_CallbackMakesTraitNeutral(SLiMEidosBlock *mutationEffect_callback { // This method checks whether a mutationEffect() callback makes the trait that it refers to neutral. // The callback has to apply globally to all subpopulations (but not necessarily to all traits). - slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; - -#if DEBUG - if (mutation_type_id == -1) - EIDOS_TERMINATION << "ERROR (Population::_CallbackMakesTraitNeutral): (internal error) mutationEffect() callback has no mutation type id." << EidosTerminate(); -#endif - bool makes_neutral = false; // we consider it non-neutral if it doesn't apply to all subpops if (mutationEffect_callback->subpopulation_id_ == -1) @@ -874,11 +867,6 @@ void Species::ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcula } // Now we do the actual validation of caches, under the newly chosen caching regime -#if (SLIMPROFILING == 1) - // PROFILING - int64_t recached_count = 0; -#endif - MutationBlock *mutation_block = mutation_block_; Mutation *mut_block_ptr = mutation_block->mutation_buffer_; @@ -887,6 +875,11 @@ void Species::ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcula bool all_nonneutral_caches_invalid_for_chromosome = all_nonneutral_caches_invalid_; TraitCalculationRegime trait_calculation_regime_for_chromosome = current_trait_calculation_regime_; +#if SLIM_PROFILE_NONNEUTRAL_CACHES() + // PROFILING + int64_t recached_count = 0; +#endif + if (DoingAnyMutationRunExperiments()) chromosome->StartMutationRunExperimentClock(); int mutrun_context_count = chromosome->ChromosomeMutationRunContextCount(); @@ -927,20 +920,20 @@ void Species::ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcula __attribute__ ((unused)) int64_t this_recached_count = (this->*(_ValidateNonNeutralCachesForMutationRunPool_TEMPLATED))(mutrun_pool, mut_block_ptr); -#if (SLIMPROFILING == 1) +#if SLIM_PROFILE_NONNEUTRAL_CACHES() // PROFILING recached_count += this_recached_count; #endif } if (DoingAnyMutationRunExperiments()) chromosome->StopMutationRunExperimentClock("ValidateNonNeutralCaches()"); + +#if SLIM_PROFILE_NONNEUTRAL_CACHES() + chromosome->profile_mutrun_nonneutral_recache_total_ += recached_count; +#endif } all_nonneutral_caches_invalid_ = false; - -#if (SLIMPROFILING == 1) -#warning do something with recached_count here; see old recached_run_ flag code -#endif } template @@ -5325,7 +5318,7 @@ void Species::ReturnShuffleBuffer(void) void Species::CollectMutationProfileInfo(void) { // maintain our history of the nonneutral regime - profile_nonneutral_regime_history_.emplace_back(last_nonneutral_regime_); + profile_trait_calculation_regime_history_.emplace_back(current_trait_calculation_regime_); // track the maximum number of mutations in existence at one time int registry_size; @@ -5375,8 +5368,7 @@ void Species::CollectMutationProfileInfo(void) // tally the total and nonneutral mutations mutrun->tally_nonneutral_mutations(&chromosome->profile_mutation_total_usage_, - &chromosome->profile_nonneutral_mutation_total_, - &chromosome->profile_mutrun_nonneutral_recache_total_); + &chromosome->profile_nonneutral_mutation_total_); } } } diff --git a/core/species.h b/core/species.h index 23047217..5d139dd2 100644 --- a/core/species.h +++ b/core/species.h @@ -426,7 +426,7 @@ class Species : public EidosDictionaryUnretained SLiMMemoryUsage_Species profile_total_memory_usage_Species; #if SLIM_PROFILE_NONNEUTRAL_CACHES() - std::vector profile_nonneutral_regime_history_; // a record of the nonneutral regime used in each cycle + std::vector profile_trait_calculation_regime_history_; // a record of the nonneutral regime used in each cycle int64_t profile_max_mutation_index_; // the largest mutation index seen over the course of the profile #endif // SLIM_PROFILE_NONNEUTRAL_CACHES() #endif // (SLIMPROFILING == 1) From 396a7bb1c38e13d6f5292b65911c1443d8c2fb83 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 19 Jan 2026 12:57:17 -0600 Subject: [PATCH 083/107] add support for independent dominance in trait calculations --- VERSIONS | 1 + core/community.cpp | 2 + core/haplosome.cpp | 29 +-- core/haplosome.h | 13 +- core/individual.cpp | 517 ++++++++++++++++++++++++++++++------------ core/individual.h | 7 + core/mutation_block.h | 6 +- core/mutation_run.cpp | 163 +++++++++++-- core/mutation_run.h | 435 +++++++++++++++++++---------------- core/population.cpp | 6 +- core/slim_globals.h | 1 + core/species.cpp | 380 +++++++++++++++++++++++-------- core/species.h | 14 +- core/trait.h | 15 +- 14 files changed, 1103 insertions(+), 486 deletions(-) diff --git a/VERSIONS b/VERSIONS index e85f4ace..ac875e57 100644 --- a/VERSIONS +++ b/VERSIONS @@ -146,6 +146,7 @@ multitrait branch: add [Niso$ trait = NULL] parameter to registerMutationEffectCallback() to allow the trait specifier to be given (defaults to NULL for backward compatibility) add a new return option for mutationEffect() callbacks: NULL now indicates neutrality for the focal trait, meaning either 1.0 (multiplicative) or 0.0 (additive) re-enable non-neutral caching (but without independent dominance optimizations, so far) + add calculation and use of independent dominance caches, per trait version 5.1 (Eidos version 4.1): diff --git a/core/community.cpp b/core/community.cpp index 6e138304..a8b3f602 100644 --- a/core/community.cpp +++ b/core/community.cpp @@ -3505,8 +3505,10 @@ void Community::StartProfiling(void) // call this first, purely for its side effect of emptying out any pending profile counts // note that the accumulators governed by this method get zeroed out down below +#if SLIM_PROFILE_NONNEUTRAL_CACHES() for (Species *focal_species : all_species_) focal_species->CollectMutationProfileInfo(); +#endif // zero out profile counts for cycle stages for (int i = 0; i < 9; ++i) diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 8209ecc8..441bfe37 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -43,8 +43,9 @@ #pragma mark - // Static class variables in support of Haplosome's bulk operation optimization; see Haplosome::WillModifyRunForBulkOperation() -int64_t Haplosome::s_bulk_operation_id_ = 0; -slim_mutrun_index_t Haplosome::s_bulk_operation_mutrun_index_ = -1; +bool Haplosome::s_bulk_operation_in_progress_ = false; +slim_operation_id_t Haplosome::s_bulk_operation_id_; +slim_mutrun_index_t Haplosome::s_bulk_operation_mutrun_index_; SLiMBulkOperationHashTable Haplosome::s_bulk_operation_runs_; @@ -122,11 +123,11 @@ MutationRun *Haplosome::WillModifyRun_UNSHARED(slim_mutrun_index_t p_run_index, } } -void Haplosome::BulkOperationStart(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index) +void Haplosome::BulkOperationStart(slim_operation_id_t p_operation_id, slim_mutrun_index_t p_mutrun_index) { THREAD_SAFETY_IN_ACTIVE_PARALLEL("Haplosome::BulkOperationStart(): s_bulk_operation_id_"); - if (s_bulk_operation_id_ != 0) + if (s_bulk_operation_in_progress_) { //EIDOS_TERMINATION << "ERROR (Haplosome::BulkOperationStart): (internal error) unmatched bulk operation start." << EidosTerminate(); @@ -140,14 +141,17 @@ void Haplosome::BulkOperationStart(int64_t p_operation_id, slim_mutrun_index_t p Haplosome::BulkOperationEnd(s_bulk_operation_id_, s_bulk_operation_mutrun_index_); } + s_bulk_operation_in_progress_ = true; s_bulk_operation_id_ = p_operation_id; s_bulk_operation_mutrun_index_ = p_mutrun_index; } -MutationRun *Haplosome::WillModifyRunForBulkOperation(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index, MutationRunContext &p_mutrun_context) +MutationRun *Haplosome::WillModifyRunForBulkOperation(slim_operation_id_t p_operation_id, slim_mutrun_index_t p_mutrun_index, MutationRunContext &p_mutrun_context) { THREAD_SAFETY_IN_ACTIVE_PARALLEL("Haplosome::WillModifyRunForBulkOperation(): s_bulk_operation_id_"); + if (!s_bulk_operation_in_progress_) + EIDOS_TERMINATION << "ERROR (Haplosome::WillModifyRunForBulkOperation): (internal error) called with no bulk operation in progress." << EidosTerminate(); if (p_mutrun_index != s_bulk_operation_mutrun_index_) EIDOS_TERMINATION << "ERROR (Haplosome::WillModifyRunForBulkOperation): (internal error) incorrect run index during bulk operation." << EidosTerminate(); if (p_mutrun_index >= mutrun_count_) @@ -198,15 +202,14 @@ MutationRun *Haplosome::WillModifyRunForBulkOperation(int64_t p_operation_id, sl #endif } -void Haplosome::BulkOperationEnd(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index) +void Haplosome::BulkOperationEnd(slim_operation_id_t p_operation_id, slim_mutrun_index_t p_mutrun_index) { THREAD_SAFETY_IN_ACTIVE_PARALLEL("Haplosome::BulkOperationEnd(): s_bulk_operation_id_"); - if ((p_operation_id == s_bulk_operation_id_) && (p_mutrun_index == s_bulk_operation_mutrun_index_)) + if (s_bulk_operation_in_progress_ && (p_operation_id == s_bulk_operation_id_) && (p_mutrun_index == s_bulk_operation_mutrun_index_)) { s_bulk_operation_runs_.clear(); - s_bulk_operation_id_ = 0; - s_bulk_operation_mutrun_index_ = -1; + s_bulk_operation_in_progress_ = false; } else { @@ -214,7 +217,7 @@ void Haplosome::BulkOperationEnd(int64_t p_operation_id, slim_mutrun_index_t p_m } } -void Haplosome::TallyHaplosomeReferences_Checkback(slim_refcount_t *p_mutrun_ref_tally, slim_refcount_t *p_mutrun_tally, int64_t p_operation_id) +void Haplosome::TallyHaplosomeReferences_Checkback(slim_refcount_t *p_mutrun_ref_tally, slim_refcount_t *p_mutrun_tally, slim_operation_id_t p_operation_id) { #if DEBUG if (mutrun_count_ == 0) @@ -2547,7 +2550,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addMutations(EidosGlobalStringID p_ continue; // We have not yet processed this mutation run; do this mutation run index as a bulk operation - int64_t operation_id = MutationRun::GetNextOperationID(); + slim_operation_id_t operation_id = MutationRun::GetNextOperationID(); Haplosome::BulkOperationStart(operation_id, mutrun_index); @@ -2912,7 +2915,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID for (slim_mutrun_index_t mutrun_index : mutrun_indexes) { - int64_t operation_id = MutationRun::GetNextOperationID(); + slim_operation_id_t operation_id = MutationRun::GetNextOperationID(); std::vector mutations_to_add; // Before starting the bulk operation for this mutation run, construct all of the mutations and add them all to the registry, etc. @@ -4553,7 +4556,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID continue; // We have not yet processed this mutation run; do this mutation run index as a bulk operation - int64_t operation_id = MutationRun::GetNextOperationID(); + slim_operation_id_t operation_id = MutationRun::GetNextOperationID(); Haplosome::BulkOperationStart(operation_id, mutrun_index); diff --git a/core/haplosome.h b/core/haplosome.h index 87b6bba3..1691a483 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -134,7 +134,8 @@ class Haplosome : public EidosObject // Bulk operation optimization; see WillModifyRunForBulkOperation(). The idea is to keep track of changes to MutationRun // objects in a bulk operation, and short-circuit the operation for all haplosomes with the same initial MutationRun (since // the bulk operation will produce the same product MutationRun given the same initial MutationRun). Note this is shared by all species. - static int64_t s_bulk_operation_id_; + static bool s_bulk_operation_in_progress_; + static slim_operation_id_t s_bulk_operation_id_; static slim_mutrun_index_t s_bulk_operation_mutrun_index_; static SLiMBulkOperationHashTable s_bulk_operation_runs_; @@ -281,12 +282,12 @@ class Haplosome : public EidosObject // nothing about the operation being performed; it just plays around with MutationRun pointers, recognizing when the runs are // identical. The first call for a new operation ID will always return a pointer, and the caller will then perform the operation; // subsequent calls for haplosomes with the same starting MutationRun will substitute the same final MutationRun and return nullptr. - static void BulkOperationStart(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index); - MutationRun *WillModifyRunForBulkOperation(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index, MutationRunContext &p_mutrun_context); - static void BulkOperationEnd(int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index); + static void BulkOperationStart(slim_operation_id_t p_operation_id, slim_mutrun_index_t p_mutrun_index); + MutationRun *WillModifyRunForBulkOperation(slim_operation_id_t p_operation_id, slim_mutrun_index_t p_mutrun_index, MutationRunContext &p_mutrun_context); + static void BulkOperationEnd(slim_operation_id_t p_operation_id, slim_mutrun_index_t p_mutrun_index); // Remove all mutations in p_haplosome that have a state_ of MutationState::kFixedAndSubstituted, indicating that they have fixed - inline __attribute__((always_inline)) void RemoveFixedMutations(Mutation *p_mut_block_ptr, int64_t p_operation_id, slim_mutrun_index_t p_mutrun_index) + inline __attribute__((always_inline)) void RemoveFixedMutations(Mutation *p_mut_block_ptr, slim_operation_id_t p_operation_id, slim_mutrun_index_t p_mutrun_index) { #if DEBUG if (mutrun_count_ == 0) @@ -302,7 +303,7 @@ class Haplosome : public EidosObject } // TallyHaplosomeReferences_Checkback() counts up the total MutationRun references, using their usage counts, as a checkback - void TallyHaplosomeReferences_Checkback(slim_refcount_t *p_mutrun_ref_tally, slim_refcount_t *p_mutrun_tally, int64_t p_operation_id); + void TallyHaplosomeReferences_Checkback(slim_refcount_t *p_mutrun_ref_tally, slim_refcount_t *p_mutrun_tally, slim_operation_id_t p_operation_id); inline __attribute__((always_inline)) int mutation_count(void) const // used to be called size(); renamed to avoid confusion with MutationRun::size() and break code using the wrong method { diff --git a/core/individual.cpp b/core/individual.cpp index 602181b8..6d5f2396 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -6438,7 +6438,7 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; - if (trait->is_pure_neutral_now) + if (trait->is_pure_neutral_now_) { TraitType traitType = trait->Type(); slim_effect_t trait_baseline_offset = trait->BaselineOffset(); @@ -6487,9 +6487,9 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual // species (the top-level loop to make mutation run experiment timing simple), then over the traits provided // (to avoid having to test for additive vs. multiplicative over and over for each mutation), then over // the individuals (the level at which parallelization of the code occurs). For each individual, it - // dispatches to a sub-method that computes the trait value produced by all of the mutations for the given - // chromosome/trait/individual. This method then aggregates those values together to produce the final - // trait values, which are saved into the phenotype storage of each individual. + // sets an initial trait value based on the baseline offset and individual offset. It then dispatches to + // a sub-method that computes and aggregates the trait effect produced by all of the mutations for the given + // chromosome/trait/individual, ultimately producing a final trait values for each individual. // First we cache a vector of mutationEffect() callbacks for each subpop; we do this here, rather than at the // start of each tick, so that newly registered callbacks function, and the current active state of each @@ -6531,6 +6531,8 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual } #endif + // Note that our caller has already validated non-neutral caches and independent-dominance effects. + // If a trait is known to be "pure neutral" -- neutral even including the effects of callbacks, and not // involving any non-neutral callbacks that need to be called for side effects -- then we want to handle // the trait up front here and remove it from the vector of trait indices, for efficiency downstream. @@ -6676,8 +6678,6 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual } } - // Note that our caller has already validated non-neutral caches and independent-dominance effects. - // For a given individual, for a given trait, we have to make a decision as to whether we will recalculate or not. That decision gets made // once and then holds across all chromosomes for the individual. But we're looping over chromosomes at the topmost level, so we have a // little problem: how will we remember whether we decided to recalculate a given individual/trait when we get to doing the work for @@ -6700,6 +6700,10 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual TraitType traitType = trait->Type(); slim_effect_t trait_baseline_offset = trait->BaselineOffset(); +#if DEBUG_TRAIT_DEMAND() + std::cout << " DemandPhenotype_INDIVIDUALS() trait " << trait->Name() << " (" << traitType << ") has baseline offset " << trait_baseline_offset << std::endl; +#endif + if (traitType == TraitType::kAdditive) { for (int individual_index = 0; individual_index < individuals_count; ++individual_index) @@ -6707,6 +6711,10 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual Individual *ind = individuals_buffer[individual_index]; IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; +#if DEBUG_TRAIT_DEMAND() + //std::cout << " individual #" << individual_index << " offset " << trait_info.offset_ << std::endl; +#endif + if (f_force_recalc) { trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; @@ -6726,6 +6734,10 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual Individual *ind = individuals_buffer[individual_index]; IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; +#if DEBUG_TRAIT_DEMAND() + //std::cout << " individual #" << individual_index << " offset " << trait_info.offset_ << std::endl; +#endif + if (f_force_recalc) { trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; @@ -6740,8 +6752,9 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual } } - // calculate the specified phenotypes for the individual; this loops through all chromosomes, handling ploidy and callbacks as needed - // it is very nice to have the top-level loop be over the chromosomes, so that each one can do a single timing for mutrun experiments + // Calculate the specified phenotypes for the individuals; this loops through all chromosomes, handling + // ploidy and callbacks as needed. It is very nice to have the top-level loop be over the chromosomes, + // so that each one can do a single timing for mutrun experiments. int haplosome_index = 0; for (Chromosome *chromosome : species->Chromosomes()) @@ -6760,9 +6773,24 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual { slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; + TraitType traitType = trait->Type(); + + // Cache a method pointer for incorporating independent dominance effects here too +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + + void (Individual::*_IncorporateEffects_IndependentDominance_TEMPLATED)(Haplosome *haplosome, slim_trait_index_t trait_index) = nullptr; + + if (traitType == TraitType::kAdditive) + _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; + else + _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; + +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() #if DEBUG_TRAIT_DEMAND() - std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->Symbol() << "'" << std::endl; + int total_individuals_recalculated = 0, independent_dominance_individuals = 0; #endif for (int individual_index = 0; individual_index < individuals_count; ++individual_index) @@ -6799,54 +6827,34 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual } else { - // FIXME MULTITRAIT: with no callbacks, check for the "independent dominance" case here and short-circuit the calculations for all individuals - // to just pull pre-calculated values from the mutation runs of the individual; can that just be another templated variant, actually? - // NOTE: when a given mutation run is hemizygous the "independent dominance" cache can be used, even if the mutations in it - // would not be "independent dominance" when found in the diploid state. Similarly, haploid chromosomes are ALWAYS in an - // "independent dominance" state, and should always use those caches. So that mechanism is actually quite general, and needs - // to be incorporated broadly into this design! When do those cached values get calculated, when do they get used, and how will - // that interact with parallelization? This depends somewhat on non-neutral caching; we might want separate non-neutral caches - // for mutations that are vs. are not "independent dominance", and so we might want a flag on Mutation that indicates that, for - // fast sorting of mutations into the correct caches. For haplosomes that are associated with a haploid chromosome, these caches - // would be the same (all mutations are independent dominance); for haplosomes associated with a diploid chromosome, these caches - // would be different and non-intersecting, and here we would assimilate the "independent dominance" effect for all mutruns in the - // two haplosomes, and then assimilate the effects of all non-neutral mutations in the non-independent non-neutral caches. - - // FIXME MULTITRAIT: I think in the parallel case we want to just loop through all mutation runs and set up these caches ahead of - // time. To do that efficiently we should have a vector of all mutation runs for a species or a chromosome (do we have that?). - // We should also have a flag for whether any non-neutral independent-dominance mutations have ever been seen for a given - // chromosome; if not, we can skip making that cache, or even checking that per-mutation flag. It should also go the other way: - // if we have never seen a non-neutral mutation that is *not* independent dominance, we can skip making that cache or checking - // those flags. This method should maybe be templated based upon those two flags, so that it can skip thinking about one or the - // other category of mutations completely; otherwise, we would branch twice per individual, which is probably not a big deal. - - // FIXME MULTITRAIT: Ideally, all of these mechanics could be per-trait, such that maybe one trait exhibits independent dominance - // for all of its effects, and that fact gets leveraged for its calculations, whereas another trait exhibits all non-independence - // for all of its effects, and that also gets leveraged. I need to think about whether that requires separate mutrun caches for - // each trait, or whether we can get some leverage on this with just the two planned caches across all traits. I think it works - // with just the two caches? Hmm, but if the effect of a mutation is neutral for one trait and non-neutral for another, we have - // to put it in the non-neutral cache; and if the effect of a mutation is independent-dominance for one trait and not for another, - // we have to put it in the non-independent cache. So there's a lowest-common-denominator thing happening here. The only way to - // avoid that is to have separate non-neutral caches per trait, for each mutation run. Which is not out of the question if it - // allows a big speedup, skipping work for particular mutation runs for particular traits. This is a big design decision to be made. - - // FIXME MULTITRAIT: Note that the "independent dominance" optimization only works when mutationEffect() callbacks are not present. - // When they are present, rather than calling e.g. IncorporateEffects_Independent() for each haplosome, we would just call - // IncorporateEffects_Diploid() again for the independent-dominance caches as well, in some TBD manner; we would treat those - // mutations identically to the non-independent mutations, since the mutationEffect() callbacks might make them non-independent. - - // FIXME MULTITRAIT: We will want smarter cache-invalidation for these mutrun caches. Whenever a mutation is added or removed from - // a mutrun, its caches invalidate EXCEPT if the mutation being added/removed is neutral, in which case it doesn't affect the caches - // (and if it is non-neutral, it should affect only the cache it would be a member of). Whenever a mutation's effect or dominance - // change, all mutruns for that chromosome in that positional range should invalidate (since they might contain that mutation), - // EXCEPT if the mutation is known not to belong to any mutrun, which would be true for `mut` inside a mutation() callback; we need - // to be smart about that to avoid unnecessary cache invalidations. - - // diploid, both haplosomes non-null - auto IncorporateEffects_Diploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED; - (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait, subpop_trait_mutationEffect_callbacks); + // diploid, both haplosomes non-null; in this code path we can optimize for + // independent dominance since neither haplosome is hemizygous +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + if (trait->is_pure_independent_dominance_now_) + { +#if DEBUG_TRAIT_DEMAND() + independent_dominance_individuals++; +#endif + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome1, trait_index); + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome2, trait_index); + } + else +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() + { + auto IncorporateEffects_Diploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Diploid_TEMPLATED; + (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait, subpop_trait_mutationEffect_callbacks); + } } +#if DEBUG_TRAIT_DEMAND() + total_individuals_recalculated++; +#endif } + +#if DEBUG_TRAIT_DEMAND() + std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->Symbol() << "' : " << total_individuals_recalculated << " individuals recalculated (" << independent_dominance_individuals << " independent dominance)" << std::endl; +#endif } break; } @@ -6867,28 +6875,73 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual { slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; + TraitType traitType = trait->Type(); + + // Cache a method pointer for incorporating independent dominance effects here too +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + + void (Individual::*_IncorporateEffects_IndependentDominance_TEMPLATED)(Haplosome *haplosome, slim_trait_index_t trait_index) = nullptr; + + if (traitType == TraitType::kAdditive) + _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; + else + _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; #if DEBUG_TRAIT_DEMAND() - std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->Symbol() << "'" << std::endl; + int total_individuals_recalculated = 0, independent_dominance_individuals = 0; #endif - for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + if (trait->is_pure_independent_dominance_now_) { - Individual *ind = individuals_buffer[individual_index]; - Haplosome *haplosome = ind->haplosomes_[haplosome_index + ((chromosome->Type() == ChromosomeType::kNullY_YSexChromosomeWithNull) ? 1 : 0)]; - - if (haplosome->IsNull()) - continue; - if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) - continue; - - Subpopulation *subpop = ind->subpopulation_; - Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; - std::vector &subpop_trait_mutationEffect_callbacks = subpop_trait_caches.mutationEffect_callbacks_per_trait; - auto IncorporateEffects_Haploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED; - - (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait, subpop_trait_mutationEffect_callbacks); + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + Haplosome *haplosome = ind->haplosomes_[haplosome_index + ((chromosome->Type() == ChromosomeType::kNullY_YSexChromosomeWithNull) ? 1 : 0)]; + + if (haplosome->IsNull()) + continue; + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome, trait_index); + +#if DEBUG_TRAIT_DEMAND() + total_individuals_recalculated++; + independent_dominance_individuals++; +#endif + } } + else +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + Haplosome *haplosome = ind->haplosomes_[haplosome_index + ((chromosome->Type() == ChromosomeType::kNullY_YSexChromosomeWithNull) ? 1 : 0)]; + + if (haplosome->IsNull()) + continue; + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + Subpopulation *subpop = ind->subpopulation_; + Subpopulation::PerTraitSubpopCaches &subpop_trait_caches = subpop->per_trait_subpop_caches_[trait_index]; + std::vector &subpop_trait_mutationEffect_callbacks = subpop_trait_caches.mutationEffect_callbacks_per_trait; + auto IncorporateEffects_Haploid_TEMPLATED = subpop_trait_caches.IncorporateEffects_Haploid_TEMPLATED; + + (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait, subpop_trait_mutationEffect_callbacks); + +#if DEBUG_TRAIT_DEMAND() + total_individuals_recalculated++; +#endif + } + } + +#if DEBUG_TRAIT_DEMAND() + std::cout << " DemandPhenotype_INDIVIDUALS() calculating trait " << species->Traits()[trait_index]->Name() << " for chromosome '" << chromosome->Symbol() << "' : " << total_individuals_recalculated << " individuals recalculated (" << independent_dominance_individuals << " independent dominance)" << std::endl; +#endif } break; } @@ -6936,9 +6989,10 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait - // calculations, so I use a larger threshold here of 1e-6; we'll see how this does in practice. - if (std::abs(calculated_phenotype - check_phenotype) > (slim_effect_t)1e-6) - EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) phenotype check failed (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); + // calculations, so I use a larger threshold here of 1e-5; we'll see how this does in practice. + // The goal is not to check for exact equality, but to find bugs that make calculations incorrect. + if (std::abs(calculated_phenotype - check_phenotype) > (slim_effect_t)1e-5) + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) phenotype check failed for trait " << species->Traits()[trait_index]->Name() << " (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); } } #endif @@ -6960,9 +7014,9 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s // species (the top-level loop to make mutation run experiment timing simple), then over the traits provided // (to avoid having to test for additive vs. multiplicative over and over for each mutation), then over // the individuals (the level at which parallelization of the code occurs). For each individual, it - // dispatches to a sub-method that computes the trait value produced by all of the mutations for the given - // chromosome/trait/individual. This method then aggregates those values together to produce the final - // trait values, which are saved into the phenotype storage of each individual. + // sets an initial trait value based on the baseline offset and individual offset. It then dispatches to + // a sub-method that computes and aggregates the trait effect produced by all of the mutations for the given + // chromosome/trait/individual, ultimately producing a final trait values for each individual. // This method is passed p_subpop_mutationEffect_callbacks, a subpop-specific set of mutationEffect() // callbacks, and does not use the subpopulation per-trait caches used by DemandPhenotype_INDIVIDUALS(). @@ -7053,8 +7107,9 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s } } - // calculate the specified phenotypes for the individual; this loops through all chromosomes, handling ploidy and callbacks as needed - // it is very nice to have the top-level loop be over the chromosomes, so that each one can do a single timing for mutrun experiments + // Calculate the specified phenotypes for the individuals; this loops through all chromosomes, handling + // ploidy and callbacks as needed. It is very nice to have the top-level loop be over the chromosomes, + // so that each one can do a single timing for mutrun experiments. int haplosome_index = 0; for (Chromosome *chromosome : species->Chromosomes()) @@ -7152,8 +7207,22 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s } } + // Cache a method pointer for incorporating independent dominance effects here too +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + + void (Individual::*_IncorporateEffects_IndependentDominance_TEMPLATED)(Haplosome *haplosome, slim_trait_index_t trait_index) = nullptr; + + if (traitType == TraitType::kAdditive) + _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; + else + _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; + +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() + #if DEBUG_TRAIT_DEMAND() - std::cout << " DemandPhenotype_SUBPOP() calculating trait " << trait->Name() << " for chromosome '" << chromosome->Symbol() << "'" << std::endl; + int total_individuals_recalculated = 0, independent_dominance_individuals = 0; #endif // Then process the chromosome for the focal trait @@ -7193,15 +7262,29 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s } else { - // diploid, both haplosomes non-null + // diploid, both haplosomes non-null; in this code path we can optimize for + // independent dominance since neither haplosome is hemizygous +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + if (trait->is_pure_independent_dominance_now_) + { #if DEBUG_TRAIT_DEMAND() - //std::cout << " individual #" << individual_index << " existing trait value == " << ind->trait_info_[trait_index].phenotype_ << std::endl; + independent_dominance_individuals++; #endif - (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait, subpop_per_trait_mutationEffect_callbacks); + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome1, trait_index); + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome2, trait_index); + } + else +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() + { + (ind->*IncorporateEffects_Diploid_TEMPLATED)(species, haplosome1, haplosome2, trait, subpop_per_trait_mutationEffect_callbacks); + } + } + #if DEBUG_TRAIT_DEMAND() - //std::cout << " individual #" << individual_index << " updated trait value == " << ind->trait_info_[trait_index].phenotype_ << std::endl; + total_individuals_recalculated++; #endif - } } break; } @@ -7218,21 +7301,56 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s case ChromosomeType::kHNull_HaploidAutosomeWithNull: case ChromosomeType::kNullY_YSexChromosomeWithNull: { - for (int individual_index = 0; individual_index < individuals_count; ++individual_index) +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + if (trait->is_pure_independent_dominance_now_) { - Individual *ind = individuals_buffer[individual_index]; - Haplosome *haplosome = ind->haplosomes_[haplosome_index + ((chromosome->Type() == ChromosomeType::kNullY_YSexChromosomeWithNull) ? 1 : 0)]; - - if (haplosome->IsNull()) - continue; - if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) - continue; - - (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait, subpop_per_trait_mutationEffect_callbacks); + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + Haplosome *haplosome = ind->haplosomes_[haplosome_index + ((chromosome->Type() == ChromosomeType::kNullY_YSexChromosomeWithNull) ? 1 : 0)]; + + if (haplosome->IsNull()) + continue; + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome, trait_index); + +#if DEBUG_TRAIT_DEMAND() + total_individuals_recalculated++; + independent_dominance_individuals++; +#endif + } + } + else +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + Haplosome *haplosome = ind->haplosomes_[haplosome_index + ((chromosome->Type() == ChromosomeType::kNullY_YSexChromosomeWithNull) ? 1 : 0)]; + + if (haplosome->IsNull()) + continue; + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + (ind->*IncorporateEffects_Haploid_TEMPLATED)(species, haplosome, trait, subpop_per_trait_mutationEffect_callbacks); + +#if DEBUG_TRAIT_DEMAND() + total_individuals_recalculated++; +#endif + } + break; } - break; } } + +#if DEBUG_TRAIT_DEMAND() + std::cout << " DemandPhenotype_SUBPOP() calculating trait " << trait->Name() << " for chromosome '" << chromosome->Symbol() << "' : " << total_individuals_recalculated << " individuals recalculated (" << independent_dominance_individuals << " independent dominance)" << std::endl; +#endif } if (species->DoingAnyMutationRunExperiments()) @@ -7260,9 +7378,10 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait - // calculations, so I use a larger threshold here of 1e-6; we'll see how this does in practice. - if (std::abs(calculated_phenotype - check_phenotype) > (slim_effect_t)1e-6) - EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_SUBPOP): (internal error) phenotype check failed (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); + // calculations, so I use a larger threshold here of 1e-5; we'll see how this does in practice. + // The goal is not to check for exact equality, but to find bugs that make calculations incorrect. + if (std::abs(calculated_phenotype - check_phenotype) > (slim_effect_t)1e-5) + EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_SUBPOP): (internal error) phenotype check failed for trait " << species->Traits()[trait_index]->Name() << " (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); } } #endif @@ -7282,6 +7401,12 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos // This method assumes that haplosome is not a null haplosome; the caller needs to guarantee this if (haplosome->IsNull()) EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) null haplosome." << EidosTerminate(); + if (f_additiveTrait != (trait->Type() == TraitType::kAdditive)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) f_additiveTrait set incorrectly." << EidosTerminate(); + if (f_callbacks != (p_mutationEffect_callbacks.size() > 0)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) f_callbacks set incorrectly." << EidosTerminate(); + if (f_singlecallback != (p_mutationEffect_callbacks.size() == 1)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) f_singlecallback set incorrectly." << EidosTerminate(); #endif // we just need to scan through the haplosome and account for its mutations, using the homozygous mutation @@ -7302,7 +7427,9 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos MutationBlock *mutation_block = species->SpeciesMutationBlock(); Mutation *mut_block_ptr = mutation_block->mutation_buffer_; const int32_t mutrun_count = haplosome->mutrun_count_; - slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + // do internal math using double to avoid numerical error + double effect_accumulator = (double)trait_info_[trait_index].phenotype_; // start with the existing phenotype for (int run_index = 0; run_index < mutrun_count; ++run_index) { @@ -7312,7 +7439,7 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome_iter, *haplosome_max; - mutrun->beginend_nonneutral_pointers(&haplosome_iter, &haplosome_max); + mutrun->beginend_nonneutral_pointers(&haplosome_iter, &haplosome_max, species->TraitCount()); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); @@ -7332,7 +7459,7 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, trait, effect, p_mutationEffect_callbacks, haplosome->individual_); - effect_accumulator += effect; + effect_accumulator += (double)effect; } else // multiplicative { @@ -7346,12 +7473,12 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos } } - effect_accumulator *= effect; + effect_accumulator *= (double)effect; } } } - trait_info_[trait_index].phenotype_ = effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; } template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); @@ -7377,6 +7504,12 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos // This method assumes that haplosome1 and haplosome2 are not null; the caller needs to guarantee this if (haplosome1->IsNull() || haplosome2->IsNull()) EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) null haplosome." << EidosTerminate(); + if (f_additiveTrait != (trait->Type() == TraitType::kAdditive)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) f_additiveTrait set incorrectly." << EidosTerminate(); + if (f_callbacks != (p_mutationEffect_callbacks.size() > 0)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) f_callbacks set incorrectly." << EidosTerminate(); + if (f_singlecallback != (p_mutationEffect_callbacks.size() == 1)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) f_singlecallback set incorrectly." << EidosTerminate(); #endif // both haplosomes are non-null, so we need to scan through and figure out which mutations are @@ -7397,7 +7530,9 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos MutationBlock *mutation_block = species->SpeciesMutationBlock(); Mutation *mut_block_ptr = mutation_block->mutation_buffer_; const int32_t mutrun_count = haplosome1->mutrun_count_; - slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + // do internal math using double to avoid numerical error + double effect_accumulator = (double)trait_info_[trait_index].phenotype_; // start with the existing phenotype for (int run_index = 0; run_index < mutrun_count; ++run_index) { @@ -7408,8 +7543,8 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; - mutrun1->beginend_nonneutral_pointers(&haplosome1_iter, &haplosome1_max); - mutrun2->beginend_nonneutral_pointers(&haplosome2_iter, &haplosome2_max); + mutrun1->beginend_nonneutral_pointers(&haplosome1_iter, &haplosome1_max, species->TraitCount()); + mutrun2->beginend_nonneutral_pointers(&haplosome2_iter, &haplosome2_max, species->TraitCount()); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); @@ -7438,7 +7573,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -7452,7 +7587,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos } } - effect_accumulator *= heterozygous_effect; + effect_accumulator *= (double)heterozygous_effect; } if (++haplosome1_iter == haplosome1_max) @@ -7473,7 +7608,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -7487,7 +7622,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos } } - effect_accumulator *= heterozygous_effect; + effect_accumulator *= (double)heterozygous_effect; } if (++haplosome2_iter == haplosome2_max) @@ -7522,7 +7657,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, trait, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += homozygous_effect; + effect_accumulator += (double)homozygous_effect; } else // multiplicative { @@ -7536,7 +7671,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos } } - effect_accumulator *= homozygous_effect; + effect_accumulator *= (double)homozygous_effect; } goto homozygousExit1; } @@ -7554,7 +7689,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -7568,7 +7703,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos } } - effect_accumulator *= heterozygous_effect; + effect_accumulator *= (double)heterozygous_effect; } } @@ -7609,7 +7744,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -7623,7 +7758,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos } } - effect_accumulator *= heterozygous_effect; + effect_accumulator *= (double)heterozygous_effect; } } @@ -7661,7 +7796,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -7675,7 +7810,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos } } - effect_accumulator *= heterozygous_effect; + effect_accumulator *= (double)heterozygous_effect; } } @@ -7691,7 +7826,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -7705,12 +7840,12 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos } } - effect_accumulator *= heterozygous_effect; + effect_accumulator *= (double)heterozygous_effect; } } } - trait_info_[trait_index].phenotype_ = effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; } template void Individual::_IncorporateEffects_Diploid(Species *, Haplosome *, Haplosome *, Trait *, std::vector &); @@ -7721,6 +7856,43 @@ template void Individual::_IncorporateEffects_Diploid(Species template void Individual::_IncorporateEffects_Diploid(Species *, Haplosome *, Haplosome *, Trait *, std::vector &); +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + +template +void Individual::_IncorporateEffects_IndependentDominance(Haplosome *haplosome, slim_trait_index_t trait_index) +{ +#if DEBUG + // This method assumes that haplosome is not a null haplosome; the caller needs to guarantee this + if (haplosome->IsNull()) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_IndependentDominance): (internal error) null haplosome." << EidosTerminate(); +#endif + + const int32_t mutrun_count = haplosome->mutrun_count_; + + // do internal math using double to avoid numerical error + double effect_accumulator = (double)trait_info_[trait_index].phenotype_; // start with the existing phenotype + + for (int run_index = 0; run_index < mutrun_count; ++run_index) + { + const MutationRun *mutrun = haplosome->mutruns_[run_index]; + + if (f_additiveTrait) + effect_accumulator += (double)mutrun->independent_dominance_cache_for_trait(trait_index); + else + effect_accumulator *= (double)mutrun->independent_dominance_cache_for_trait(trait_index); + } + + trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; +} + +template void Individual::_IncorporateEffects_IndependentDominance(Haplosome *, slim_trait_index_t); +template void Individual::_IncorporateEffects_IndependentDominance(Haplosome *, slim_trait_index_t); + +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() + + // the rest of the code below is for checking the correctness of the calculations performed by the code above slim_effect_t Individual::_CheckPhenotypeForTrait(slim_trait_index_t trait_index) @@ -7844,11 +8016,14 @@ void Individual::_Check_IncorporateEffects_Haploid(Species *species, Haplosome * MutationBlock *mutation_block = species->SpeciesMutationBlock(); Mutation *mut_block_ptr = mutation_block->mutation_buffer_; const int32_t mutrun_count = haplosome->mutrun_count_; - slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + // do internal math using double to avoid numerical error + double effect_accumulator = (double)trait_info_[trait_index].phenotype_; // start with the existing phenotype for (int run_index = 0; run_index < mutrun_count; ++run_index) { const MutationRun *mutrun = haplosome->mutruns_[run_index]; + double mutrun_effect_accumulator = (f_additiveTrait ? 0.0 : 1.0); // Read directly from the MutationRun buffers (we do not use non-neutral caches; we evaluate everything) const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); @@ -7866,7 +8041,7 @@ void Individual::_Check_IncorporateEffects_Haploid(Species *species, Haplosome * if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, trait, effect, p_mutationEffect_callbacks, haplosome->individual_); - effect_accumulator += effect; + mutrun_effect_accumulator += (double)effect; } else // multiplicative { @@ -7880,12 +8055,31 @@ void Individual::_Check_IncorporateEffects_Haploid(Species *species, Haplosome * } } - effect_accumulator *= effect; + mutrun_effect_accumulator *= (double)effect; } } + +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + // compare the computed mutrun effect to the cached independent dominance effect, looking for large + // difference that are indicative of a bug somewhere, rather than checking for an exact match + if (trait->is_pure_independent_dominance_now_) + { + double independent_dominance_effect = (double)mutrun->independent_dominance_cache_for_trait(trait_index); + + if (std::abs(mutrun_effect_accumulator - independent_dominance_effect) > 1e-5) + std::cout << " _Check_IncorporateEffects_Haploid() for trait " << species->Traits()[trait_index]->Name() << " in chromosome " << haplosome->AssociatedChromosome()->Symbol() << " : mutrun effect " << mutrun_effect_accumulator << ", cached independent-dominance effect " << independent_dominance_effect << std::endl; + } +#endif +#endif + + if (f_additiveTrait) + effect_accumulator += mutrun_effect_accumulator; + else + effect_accumulator *= mutrun_effect_accumulator; } - trait_info_[trait_index].phenotype_ = effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; } void Individual::_Check_IncorporateEffects_Hemizygous(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) @@ -7916,7 +8110,9 @@ void Individual::_Check_IncorporateEffects_Hemizygous(Species *species, Haplosom MutationBlock *mutation_block = species->SpeciesMutationBlock(); Mutation *mut_block_ptr = mutation_block->mutation_buffer_; const int32_t mutrun_count = haplosome->mutrun_count_; - slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + // do internal math using double to avoid numerical error + double effect_accumulator = (double)trait_info_[trait_index].phenotype_; // start with the existing phenotype for (int run_index = 0; run_index < mutrun_count; ++run_index) { @@ -7938,7 +8134,7 @@ void Individual::_Check_IncorporateEffects_Hemizygous(Species *species, Haplosom if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome_mutation, -1, trait, effect, p_mutationEffect_callbacks, haplosome->individual_); - effect_accumulator += effect; + effect_accumulator += (double)effect; } else // multiplicative { @@ -7952,12 +8148,12 @@ void Individual::_Check_IncorporateEffects_Hemizygous(Species *species, Haplosom } } - effect_accumulator *= effect; + effect_accumulator *= (double)effect; } } } - trait_info_[trait_index].phenotype_ = effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; } void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks) @@ -7988,12 +8184,15 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * MutationBlock *mutation_block = species->SpeciesMutationBlock(); Mutation *mut_block_ptr = mutation_block->mutation_buffer_; const int32_t mutrun_count = haplosome1->mutrun_count_; - slim_effect_t effect_accumulator = trait_info_[trait_index].phenotype_; // start with the existing phenotype + + // do internal math using double to avoid numerical error + double effect_accumulator = (double)trait_info_[trait_index].phenotype_; // start with the existing phenotype for (int run_index = 0; run_index < mutrun_count; ++run_index) { const MutationRun *mutrun1 = haplosome1->mutruns_[run_index]; const MutationRun *mutrun2 = haplosome2->mutruns_[run_index]; + double mutrun_effect_accumulator = (f_additiveTrait ? 0.0 : 1.0); // Read directly from the MutationRun buffers (we do not use non-neutral caches; we evaluate everything) const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); @@ -8021,7 +8220,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + mutrun_effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -8035,7 +8234,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * } } - effect_accumulator *= heterozygous_effect; + mutrun_effect_accumulator *= (double)heterozygous_effect; } if (++haplosome1_iter == haplosome1_max) @@ -8056,7 +8255,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + mutrun_effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -8070,7 +8269,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * } } - effect_accumulator *= heterozygous_effect; + mutrun_effect_accumulator *= (double)heterozygous_effect; } if (++haplosome2_iter == haplosome2_max) @@ -8105,7 +8304,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) homozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, true, trait, homozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += homozygous_effect; + mutrun_effect_accumulator += (double)homozygous_effect; } else // multiplicative { @@ -8119,7 +8318,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * } } - effect_accumulator *= homozygous_effect; + mutrun_effect_accumulator *= (double)homozygous_effect; } goto homozygousExit1; } @@ -8137,7 +8336,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + mutrun_effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -8151,7 +8350,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * } } - effect_accumulator *= heterozygous_effect; + mutrun_effect_accumulator *= (double)heterozygous_effect; } } @@ -8192,7 +8391,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + mutrun_effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -8206,7 +8405,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * } } - effect_accumulator *= heterozygous_effect; + mutrun_effect_accumulator *= (double)heterozygous_effect; } } @@ -8244,7 +8443,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome1_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + mutrun_effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -8258,7 +8457,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * } } - effect_accumulator *= heterozygous_effect; + mutrun_effect_accumulator *= (double)heterozygous_effect; } } @@ -8274,7 +8473,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * if (f_callbacks && (!f_singlecallback || (mutation->mutation_type_ptr_ == single_callback_mut_type))) heterozygous_effect = (slim_effect_t)subpopulation_->ApplyMutationEffectCallbacks(haplosome2_mutindex, false, trait, heterozygous_effect, p_mutationEffect_callbacks, haplosome1->individual_); - effect_accumulator += heterozygous_effect; + mutrun_effect_accumulator += (double)heterozygous_effect; } else // multiplicative { @@ -8288,12 +8487,38 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * } } - effect_accumulator *= heterozygous_effect; + mutrun_effect_accumulator *= (double)heterozygous_effect; } } + +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + // compare the computed mutrun effect to the cached independent dominance effect, looking for large + // difference that are indicative of a bug somewhere, rather than checking for an exact match + if (trait->is_pure_independent_dominance_now_) + { + double independent_dominance_effect1 = (double)mutrun1->independent_dominance_cache_for_trait(trait_index); + double independent_dominance_effect2 = (double)mutrun2->independent_dominance_cache_for_trait(trait_index); + double independent_dominance_effect; + + if (f_additiveTrait) + independent_dominance_effect = independent_dominance_effect1 + independent_dominance_effect2; + else + independent_dominance_effect = independent_dominance_effect1 * independent_dominance_effect2; + + if (std::abs(mutrun_effect_accumulator - independent_dominance_effect) > 1e-5) + std::cout << " _Check_IncorporateEffects_Diploid() for trait " << species->Traits()[trait_index]->Name() << " in chromosome " << haplosome1->AssociatedChromosome()->Symbol() << " : mutrun effect " << mutrun_effect_accumulator << ", cached independent-dominance effect " << independent_dominance_effect << std::endl; + } +#endif +#endif + + if (f_additiveTrait) + effect_accumulator += mutrun_effect_accumulator; + else + effect_accumulator *= mutrun_effect_accumulator; } - trait_info_[trait_index].phenotype_ = effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; } diff --git a/core/individual.h b/core/individual.h index 7156ad39..babbbf32 100644 --- a/core/individual.h +++ b/core/individual.h @@ -417,6 +417,13 @@ class Individual : public EidosDictionaryUnretained template void _IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks); +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + template + void _IncorporateEffects_IndependentDominance(Haplosome *haplosome, slim_trait_index_t trait_index); +#endif +#endif + // Debugging checkback for phenotype calculation; this is very slow, and does not use the non-neutral cache slim_effect_t _CheckPhenotypeForTrait(slim_trait_index_t trait_index); diff --git a/core/mutation_block.h b/core/mutation_block.h index acef443c..0c19f2d8 100644 --- a/core/mutation_block.h +++ b/core/mutation_block.h @@ -67,9 +67,9 @@ class MutationBlock void IncreaseMutationBlockCapacity(void); void ZeroRefcountBlock(MutationRun &p_mutation_registry); - inline __attribute__((always_inline)) Mutation *MutationForIndex(MutationIndex p_index) const { return mutation_buffer_ + p_index; } - inline __attribute__((always_inline)) slim_refcount_t RefcountForIndex(MutationIndex p_index) const { return refcount_buffer_[p_index]; } - inline __attribute__((always_inline)) MutationTraitInfo *TraitInfoForIndex(MutationIndex p_index) const { return trait_info_buffer_ + (p_index * trait_count_); } + inline __attribute__((always_inline)) Mutation *MutationForIndex(MutationIndex p_mutation_index) const { return mutation_buffer_ + p_mutation_index; } + inline __attribute__((always_inline)) slim_refcount_t RefcountForIndex(MutationIndex p_mutation_index) const { return refcount_buffer_[p_mutation_index]; } + inline __attribute__((always_inline)) MutationTraitInfo *TraitInfoForIndex(MutationIndex p_mutation_index) const { return trait_info_buffer_ + (p_mutation_index * trait_count_); } inline __attribute__((always_inline)) MutationTraitInfo *TraitInfoForMutation(const Mutation *p_mutation) const { diff --git a/core/mutation_run.cpp b/core/mutation_run.cpp index 743668b7..297b4fa4 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -26,7 +26,7 @@ // For doing bulk operations across all MutationRun objects; see header -int64_t MutationRun::sOperationID = 0; +slim_operation_id_t MutationRun::sOperationID = 0; std::ostream& operator<<(std::ostream& p_out, TraitCalculationRegime p_trait_type) @@ -61,8 +61,14 @@ MutationRun::~MutationRun(void) free(mutations_); #if SLIM_USE_NONNEUTRAL_CACHES() - if (nonneutral_mutations_) - free(nonneutral_mutations_); + if (nonneutral_cache_) + { + free(nonneutral_cache_); + +#if DEBUG + nonneutral_cache_ = nullptr; +#endif + } #endif } @@ -358,7 +364,8 @@ void MutationRun::_RemoveFixedMutations(Mutation *p_mut_block_ptr) #if SLIM_USE_NONNEUTRAL_CACHES() // invalidate the nonneutral mutation cache - nonneutral_mutations_count_ = -1; + if (nonneutral_cache_) + nonneutral_cache_->count_ = -1; #endif } } @@ -459,34 +466,53 @@ void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_hal #if SLIM_USE_NONNEUTRAL_CACHES() -void MutationRun::cache_nonneutral_mutations_REGIME_0(void) const +void MutationRun::cache_nonneutral_mutations_REGIME_0(slim_trait_index_t species_trait_count) const { // // Regime 0 means there are no genetic effects at all, so we can simply empty the non-neutral cache. + // FIXME MULTITRAIT: we want to avoid allocating the nonneutral cache at all, here, if it is not allocated yet // - zero_out_nonneutral_buffer(); + zero_out_nonneutral_cache(species_trait_count); } -void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const +void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const { // // Regime 1 means there are no active mutationEffect() callbacks at all, so neutrality can be assessed // simply by looking at whether the mutation itself is neutral. The mutation type is irrelevant. // - zero_out_nonneutral_buffer(); + zero_out_nonneutral_cache(species_trait_count); // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed + // because access to the nonneutral mutation buffer is complex and slow, we manage it internally here + MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + int32_t buffer_capacity = nonneutral_cache_->capacity_; + int32_t buffer_count = nonneutral_cache_->count_; + for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; Mutation *mutptr = p_mut_block_ptr + mutindex; if (!mutptr->is_neutral_for_all_traits_) - add_to_nonneutral_buffer(mutindex); + { + if (buffer_count == buffer_capacity) + { + // expand the buffer and re-fetch our local information about it + expand_nonneutral_buffer(species_trait_count); + mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + buffer_capacity = nonneutral_cache_->capacity_; + } + + *(mutation_buffer + buffer_count) = mutindex; + ++buffer_count; + } } + + nonneutral_cache_->count_ = buffer_count; } -void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const +void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const { // // Regime 2 means the only mutationEffect() callbacks are (a) constant-effect, (b) neutral (i.e., @@ -495,9 +521,14 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) // of MutationType; if it is set, the mutation is neutral because the callback is known to be // global-neutral. Otherwise, the mutation's neutral flag is reliable. // - zero_out_nonneutral_buffer(); + zero_out_nonneutral_cache(species_trait_count); // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed + // because access to the nonneutral mutation buffer is complex and slow, we manage it internally here + MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + int32_t buffer_capacity = nonneutral_cache_->capacity_; + int32_t buffer_count = nonneutral_cache_->count_; + for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; @@ -507,11 +538,24 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) // I expect many mutations would fail the first test (thus short-circuiting), whereas // few would fail the second test (i.e. actually be neutral) in a model in this regime. if ((!mutptr->mutation_type_ptr_->subject_to_mutationEffect_callback_) && !mutptr->is_neutral_for_all_traits_) - add_to_nonneutral_buffer(mutindex); + { + if (buffer_count == buffer_capacity) + { + // expand the buffer and re-fetch our local information about it + expand_nonneutral_buffer(species_trait_count); + mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + buffer_capacity = nonneutral_cache_->capacity_; + } + + *(mutation_buffer + buffer_count) = mutindex; + ++buffer_count; + } } + + nonneutral_cache_->count_ = buffer_count; } -void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const +void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const { // // Regime 3 means that there are active mutationEffect() callbacks beyond the constant neutral global @@ -523,9 +567,14 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) // is false, we know the mutation is rendered neutral by a global-neutral callback. And if the test // of subject_to_mutationEffect_callback_ was false, the mutation's neutral flag is reliable. // - zero_out_nonneutral_buffer(); + zero_out_nonneutral_cache(species_trait_count); // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed + // because access to the nonneutral mutation buffer is complex and slow, we manage it internally here + MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + int32_t buffer_capacity = nonneutral_cache_->capacity_; + int32_t buffer_count = nonneutral_cache_->count_; + for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { MutationIndex mutindex = mutations_[bufindex]; @@ -535,28 +584,91 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) if (muttypeptr->subject_to_mutationEffect_callback_) { if (muttypeptr->subject_to_non_neutral_callback_) - add_to_nonneutral_buffer(mutindex); + { + if (buffer_count == buffer_capacity) + { + // expand the buffer and re-fetch our local information about it + expand_nonneutral_buffer(species_trait_count); + mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + buffer_capacity = nonneutral_cache_->capacity_; + } + + *(mutation_buffer + buffer_count) = mutindex; + ++buffer_count; + } } else { if (!mutptr->is_neutral_for_all_traits_) - add_to_nonneutral_buffer(mutindex); + { + if (buffer_count == buffer_capacity) + { + // expand the buffer and re-fetch our local information about it + expand_nonneutral_buffer(species_trait_count); + mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + buffer_capacity = nonneutral_cache_->capacity_; + } + + *(mutation_buffer + buffer_count) = mutindex; + ++buffer_count; + } } } + + nonneutral_cache_->count_ = buffer_count; } void MutationRun::check_nonneutral_mutation_cache() const { - if (!nonneutral_mutations_) + // FIXME MULTITRAIT: I plan to relax the requirement that the nonneutral cache be allocated, but for now it is required + if (!nonneutral_cache_) EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache not allocated." << EidosTerminate(); - if (nonneutral_mutations_count_ == -1) - EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) unvalidated cache." << EidosTerminate(); - if (nonneutral_mutations_count_ > nonneutral_mutation_capacity_) - EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache size exceeds cache capacity." << EidosTerminate(); + + if (nonneutral_cache_) + { + if (nonneutral_cache_->count_ == -1) + EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) unvalidated cache." << EidosTerminate(); + if (nonneutral_cache_->count_ > nonneutral_cache_->capacity_) + EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache size exceeds cache capacity." << EidosTerminate(); + } +} + +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + +template +void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t trait_index, MutationBlock *mutation_block) const +{ + // do internal math using double to avoid numerical error + double effect_accumulator = (f_is_additive_trait ? 0.0 : 1.0); // start with neutrality + + for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) + { + MutationIndex mutindex = mutations_[bufindex]; + slim_effect_t independent_dominance_effect; + + if (f_haploid_chromosome) + independent_dominance_effect = mutation_block->TraitInfoForIndex(mutindex)[trait_index].homozygous_effect_; + else + independent_dominance_effect = mutation_block->TraitInfoForIndex(mutindex)[trait_index].heterozygous_effect_; + + if (f_is_additive_trait) + effect_accumulator += (double)independent_dominance_effect; + else + effect_accumulator *= (double)independent_dominance_effect; + } + + nonneutral_cache_->independent_dominance_cache_[trait_index] = (slim_effect_t)effect_accumulator; } +template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, MutationBlock *) const; +template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, MutationBlock *) const; +template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, MutationBlock *) const; +template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, MutationBlock *) const; + +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() #endif // SLIM_USE_NONNEUTRAL_CACHES() + // Shorthand for clear(), then copy_from_run(p_mutations_to_set), then insert_sorted_mutation() on every // mutation in p_mutations_to_add, with checks with enforce_stack_policy_for_addition(). The point of // this is speed: like HaplosomeCloned(), we can merge the new mutations in much faster if we do it in @@ -666,13 +778,14 @@ size_t MutationRun::MemoryUsageForMutationIndexBuffers(void) const return mutation_capacity_ * sizeof(MutationIndex); } -size_t MutationRun::MemoryUsageForNonneutralCaches(void) const +size_t MutationRun::MemoryUsageForNonneutralCaches(slim_trait_index_t trait_count) const { #if SLIM_USE_NONNEUTRAL_CACHES() - return nonneutral_mutation_capacity_ * sizeof(MutationIndex); -#else - return 0; + if (nonneutral_cache_) + return nonneutral_cache_->capacity_ * sizeof(MutationIndex) + sizeof(NonNeutralCache) + trait_count * sizeof(slim_effect_t); #endif + + return 0; } diff --git a/core/mutation_run.h b/core/mutation_run.h index b4119cc7..c77c1826 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -43,6 +43,7 @@ class MutationRun; +class MutationBlock; // We keep a per-species pool of freed mutation runs, and a per-species pool of in-use mutation runs. These are kept by the @@ -90,11 +91,21 @@ typedef struct MutationRunContext { // If defined as 1, MutationRun will keep a side cache of the non-neutral mutations occuring inside it. This can greatly accelerate // fitness calculations, but does consume additional memory, and is not always advantageous. Define to 0 to disable this feature. -// I'm not sure how long I will maintain the ability to disable these caches; the overhead is quite small, so I think it would be OK -// to just make this always be on. At present this flag is mostly useful for testing purposes. // // Function-like macro used for robustness: see https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ -#define SLIM_USE_NONNEUTRAL_CACHES() 1 +#define SLIM_USE_NONNEUTRAL_CACHES() 1 +#define SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() 1 + +#if (SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() && !SLIM_USE_NONNEUTRAL_CACHES()) +#error The use of independent-dominance caches requires the use of non-neutral caches. +#endif + +#if !SLIM_USE_NONNEUTRAL_CACHES() +#warning Non-neutral caches have been disabled; this may affect performance. +#endif +#if !SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#warning Independent-dominance caches have been disabled; this may affect performance. +#endif #if SLIM_USE_NONNEUTRAL_CACHES() && (SLIMPROFILING == 1) // PROFILING: this flag should be 1 to profile the use of non-neutral caches. It's a separate flag to @@ -124,13 +135,6 @@ class MutationRun { // This class has its copy constructor and assignment operator disabled, to prevent accidental copying. -private: - - // MutationRun has a marking mechanism to let us loop through all haplosomes and perform an operation on each MutationRun once. - // This counter is used to do that; a client wishing to perform such an operation should increment the counter and then use it - // in conjuction with operation_id_ below. Note this is shared by all species. - static int64_t sOperationID; // use MutationRun::GetNextOperationID() to access this - private: // MutationRun used to have an internal buffer that it could use to hold mutation pointers, to decrease malloc overhead when @@ -146,6 +150,41 @@ class MutationRun mutable EidosDebugLock mutrun_use_count_LOCK; #endif + // MutationRun has a marking mechanism to let us loop through all haplosomes and perform an operation on each + // MutationRun once. The sOperationID counter is used to do that; a client performing such an operation should + // increment the counter and then use it in conjuction with operation_id_ below. Note this is shared by all species. + // The idea is that a new and unique slim_operation_id_t value is generated by GetNextOperationID() each time + // an operation starts, and so we don't need to do a sweep to clear an "I've been processed" flag, we can just + // check the new/unique operation id against the pre-existing old/stale value to determine that. This is slightly + // dangerous, since we might wrap all the way around the 32-bit slim_operation_id_t range and come back to a value + // used previously that might still be present as an old/stale value for some mutation run. This seems EXTREMELY + // unlikely to occur in practice, however; it's a hack, but seems like a safe enough one to be tolerable, and I + // can't think of a graceful way to make it 100% safe without doing a sweep before every operation. + static slim_operation_id_t sOperationID; // use MutationRun::GetNextOperationID() to access this + +public: + + // These bits of the operation ID mechanism are public. This state is here to optimize the class layout. + mutable slim_operation_id_t operation_id_ = 0; + + static inline slim_operation_id_t GetNextOperationID(void) + { + THREAD_SAFETY_IN_ACTIVE_PARALLEL("GetNextOperationID(): MutationRun::sOperationID change"); + + slim_operation_id_t next_operation_id = ++(MutationRun::sOperationID); + +#if DEBUG + if (next_operation_id == 0) + std::cout << "### GetNextOperationID() wrapped around!" << std::endl; + + //std::cout << "### GetNextOperationID() returning " << next_operation_id << std::endl; +#endif + + return next_operation_id; + } + +private: + #if SLIM_USE_NONNEUTRAL_CACHES() // @@ -162,37 +201,51 @@ class MutationRun // represent the composite effects of all mutations that satisfy certain "independent dominance" // criteria and can thus be handled *without* knowledge of homozygosity vs. heterozygosity // - // Any given mutation will be treated in one of three ways: it will be placed in the non-neutral mutation - // buffer, it will be included in the independent-dominance effect caches, or (if it is deemed completely - // neutral) it will not be included in either since it can be ignored for purposes of trait value calculation. - // This determination is managed primarily based upon the mutation type of the mutation, because that is - // the level at which mutationEffect() callbacks operate -- they make particular mutation types become one - // of three things: (a) completely neutral, (b) non-neutral but with trait effects that are either neutral or - // independent-dominance, or (c) non-neutral with at least one trait effect that is non-neutral and non- - // independent-dominance. Category (a) is left out of the non-neutral caching entirely; category (b) is put - // into the independent-dominance effect caches; and category (c) is put into the non-neutral mutation buffer. - // Whenever the non-neutral mutation caches are about to be used -- whenever trait values are about to be - // re-calculated -- the status of all mutation types is re-checked, with respect to their intrinsic status - // due to the mutations they represent, and also how that status is modified by all of the currently active - // mutationEffect() callbacks. If the status of all of the mutation types is unchanged from the last cache - // validation, the existing cache state can be used; otherwise, the existing caches have to be thrown out and - // remade from scratch, because a mutation type's change in status means that a whole set of mutations needs - // to change how it is treated in the caches, which cannot be done incrementally. + // Any given mutation will be treated in one of two ways: it will be placed in the non-neutral mutation + // buffer, because it needs to be included in trait calculations (because it has an intrinsic non-neutral + // effect on at least one trait, or because it is influenced by a non-neutral mutationEffect() callback), + // or (if it is deemed completely neutral, including effects of callbacks) it will not be included in that + // buffer. This determination is managed primarily based upon the mutation type of the mutation, because + // that is the level at which mutationEffect() callbacks operate -- they make particular mutation types + // neutral or non-neutral for particular traits. Depending upon the configuration of callbacks, SLiM might + // be able to determine that a given mutation type is (a) completely neutral, such that it can be left out + // of the non-neutral buffer unambiguously, (b) non-neutral for at least one trait, such that it must be + // put in the non-neutral buffer unambiguously, or (c) neutral for some trait(s) but not determined to be + // neutral for all traits, such that the determination of whether a given mutation should be placed in the + // non-neutral buffer depends on the intrinsic state of that mutation, rather than being determined just + // on the basis of the mutation type itself. This is essentially the basis of the "trait calculation regime" + // represented by the TraitCalculationRegime enum. Whenever the non-neutral mutation caches are about to be + // used -- whenever trait values are about to be calculated -- the status of all mutation types is re-checked, + // with respect to their intrinsic status due to the mutations they represent, and also how that status is + // modified by all of the currently active mutationEffect() callbacks. If the status of all of the mutation + // types is unchanged from the last cache validation, the existing cache state can be used; otherwise, the + // existing caches have to be thrown out and remade from scratch, because a mutation type's change in status + // means that a whole set of mutations needs to change how it is treated in the caches, which cannot be done + // incrementally. // // The story is a little more complicated than that, because we can be a bit smarter. If a mutationEffect() // callback is present for a mutation type, that puts mutations of that mutation type firmly into category (a), - // (b), or (c), or it means that we simply don't know what the callback will do and so we're in category (c) + // (b), or (c), or it means that we simply don't know what the callback will do and so we're in category (b) // due to our lack of knowledge. But there is also the case where there are no mutationEffect() callbacks for - // a given mutation type! In this case -- probably the most common case, actually -- we only care about the + // a given mutation type. In this case -- probably the most common case, actually -- we only care about the // state of the mutation itself; its mutation type is irrelevant since it exerts no control. Here we use // precalculated flags on Mutation to guide how the mutation is handled for caching. If the mutation is - // neutral for all traits, it goes into category (a). If it is non-neutral but its non-neutral effects are - // all independent-dominance, it is category (b). If it is non-neutral with non-independent effects, it is - // category (c). + // non-neutral for at least one trait, it into the non-neutral mutation buffer, otherwise it doesn't. + // + // When considering mutationEffect() callbacks we can actually infer, in specific cases, that they make a + // mutation type neutral for some or all traits. A callback that deterministically returns 1.0 (i.e., is + // implemented simply as "return 1.0;" or similar) makes multiplicative traits neutral; one that returns 0.0 + // makes additive traits neutral; one that returns NULL makes both types of trait neutral (by definition). + // This is a fairly common pattern for making mutations of a given type neutral in one subpopulation, or for + // some period of time. One small wrinkle is that a mutation type can be affected by more than one callback; + // they are called sequentially. A callback earlier in the chain might produce a non-neutral effect on a + // trait, but then be overridden by a callback later in the chain that makes the mutation type neutral. The + // earlier callback must still be called, because it might have external side effects; we can't skip it. + // We can, however, infer that the mutation type has been made neutral for the trait(s) in question. // - // The trickiest thing is if constant-effect mutationEffect() callbacks are present that would allow an - // inference to be drawn for a particular mutation. Suppose a mutation is neutral for all traits but B; for - // B it is non-neutral. Some other mutations for the same mutation type are also non-neutral for trait A, + // A tricky situation arises if such constant-effect mutationEffect() callbacks are present that would allow + // an inference to be drawn for a particular mutation. Suppose a mutation is neutral for all traits but B; + // for B it is non-neutral. Some other mutations for the same mutation type are also non-neutral for trait A, // so the mutation type itself is known to be non-neutral for both A and B. Then a mutationEffect() callback // makes the mutation type neutral for B. The mutation type is still known to be non-neutral, since it has // mutations that are non-neutral for A. But our particular focal mutation has been rendered completely @@ -211,45 +264,15 @@ class MutationRun // that position (because they might be affected by the change), without invalidating the rest of the caches; // for this purpose, each Chromosome keeps a per-mutrun-slot "I'm valid" flag, and if one of those flags is // set to invalid, all mutation runs in the affected slot get re-validated before non-neutral caches are used. - // (FIXME MULTITRAIT: For right now, we will just have a per-Chromosome flag, which can be refined later; - // leveraging the specific mutrun slot is a little complicated because of mutrun experiments.) + // (FIXME MULTITRAIT: This is not yet implemented. As a first step, I think we will probably just have a + // per-Chromosome flag, which can be refined later; leveraging the specific mutrun slot is a little complicated + // because of mutrun experiments.) // // There are some special cases. If all mutations in the model are neutral, the non-neutral caches are not // used at all, and trait calculation can skip looking at genetics completely. If all mutations in the model - // are non-neutral (as is common in tree-seq models), then (a) if all are independent-dominance only the - // independent-dominance effect caches get set up, (b) if none are independent-dominance then the non-neutral - // caches do not get set up at all and trait calculations use the main mutation run buffers instead (no point - // in copying every mutation into an identical buffer of non-neutral mutations), or (c) if there is a mix - // of independent and non-independent-dominance, the non-neutral cache is set up as usual, since it remains - // worthwhile to divide mutations into those two categories. - // - // There is also the special case of the ploidy of particular chromosomes. The description above applies to - // mutation runs that represent diploid chromosomes. For haploid chromosomes, effects are "independent" by - // definition; ploidy is not a factor. For such mutation runs, the non-neutral mutation buffers are not - // used at all; instead, all non-neutral mutations are included in the independent-dominance effect caches, - // and all trait calculations can be based simply upon those caches (making haploid models very fast). For - // intrinsically diploid chromosomes that can be present in one copy in a given individual (like an X), the - // caching is currently done following the standard diploid approach, but with the addition of per-trait - // hemizygous effect caches kept in each mutation run as well. This adds substantial complication, but - // seems worth it given that typically 50% of individuals for a chromosome like an X would be hemizygous, - // and some models might be models of only an X; that's a lot of performance to leave on the table. This - // added complexity is only for the X and W chromosome types, however; a regular diploid autosome is also - // allowed to have null haplosomes and be hemizygous, but that situation does not receive hemizygous effect - // caches in the present design since it is not clear that it would be a win in most cases (although for - // haplodiploid models it probably would be, in fact). FIXME MULTITRAIT: Maybe we can be smarter on this... - // - // Another special case has to do with the evaluation of traits for neutrality. As described above, mutations - // with non-independent effects on at least one trait have to be put in the non-neutral mutation buffer, so - // that those effects get evaluated correctly (for heterozygous vs. homozygous effects). And normally this - // means that any independent-dominance effects the mutation might have on other traits cannot be optimized, - // since the mutation is in the non-neutral mutation buffer. There is one case where this can be finessed, - // however: when ALL mutations are known to be independent-dominance (or neutral) for a given trait. If SLiM - // can determine that, it can put the effects of all mutations for that trait into the independent-dominance - // effect cache for that trait! This setup needs to be flagged separately, and the code that calculates the - // effects of mutations in the non-neutral mutation buffer needs to see that flag and skip that trait, to - // avoid double-counting the effect of these mutations. This introduces significant additional complexity in - // the design, but it is very much worthwhile since it allows models containing both an independent trait and - // a non-independent trait to run much faster -- and that kind of setup is expected to be fairly common. + // are non-neutral (as is common in tree-seq models), then the non-neutral caches do not get set up at all + // and trait calculations use the main mutation run buffers instead (no point in copying every mutation into + // an identical buffer of non-neutral mutations). FIXME MULTITRAIT work remains here... // // There is a final special case that we do not cater to for now, and that is separate non-neutral mutation // caching behavior on a per-chromosome basis. Since any given mutation run belongs to one chromosome, we @@ -260,57 +283,92 @@ class MutationRun // all genetic calculations for most of the chromosomes in a model because we can deduce that non-neutral // effects are only present on a couple of the chromosomes, which seems like it might be common. Optimization // at this level might be added later. FIXME MULTITRAIT - // - // BCH 4/19/2023: Note that this stuff is all related to caching, so it is mutable even for immutable objects. - // BCH 1/14/2025: PLANNED CHANGES: // - // X remove nonneutral_change_validation_; now that we will pre-validate all non-neutral caches prior to use, - // this is no longer needed and just wastes space. Instead, we will simply have a flag on Species, or - // perhaps per-chromosome, that says "everything is invalid", and we will check that flag and act on it - // at validation time - // - nonneutral_mutations_: this pointer should now point first to the per-trait independent-dominance effects, - // then to the hemizygous per-trait effects if present, then to the MutationIndex buffer, sequentially. - // this is to avoid multiple allocations and multiple pointers, and to maximize memory locality. Since - // this arrangement will be very gross to work with, it should be private only to a set of helper methods - // that mask the implementation details completely. TBD: who exactly will know whether hemizygous effects - // are present, and how many traits exist, and so forth? The chromosome knows all that, or can get to it; - // but we don't have a pointer to the chromosome. Maybe all the non-neutral cache helper methods take a - // Chromosome * parameter to help those methods find their way out? But ugh. I think after we remove - // nonneutral_change_validation_ there will be room in the class layout for a little info: - // bool chromosome_type_is_hemizygous; - // uint8_t species_trait_count; - // That info would be present only when non-neutral caching is enabled. - // - the capacity and count and that info stuff should actually get moved inside the non-neutral cache pointer. - // The rationale for this is that there will be cases where we want to turn non-neutral caching off, - // entirely or per-chromosome, even though it is enabled in the build: if we know that all mutations are - // neutral, for example, or if we know that NO mutations are neutral or independent-dominance, such that - // the non-neutral cache would just be a copy of the main mutation buffer. In such cases, keeping all of - // the non-neutral cache state inside the pointer will allow us to minimize our memory footprint for all - // of the mutation runs in question. - // - to manage this within-pointer complexity we should have a struct, NonNeutralCache, that the pointer - // points to. It would have the fixed state up front, and then the variable-length state after. - mutable int32_t nonneutral_mutation_capacity_ = 0; // the capacity of nonneutral_mutations_ - mutable int32_t nonneutral_mutations_count_ = -1; // the number of entries currently used; -1 indicates an invalid cache - mutable MutationIndex *nonneutral_mutations_ = nullptr; // OWNED POINTER: a pointer to MutationIndex for non-neutral mutations + // Independent-dominance caches + // + // A sub-category of the non-neutral cache is the independent-dominance caches. "Independent dominance" is + // defined as a dominance value that makes two heterozygous effects for a given mutation be equivalent to + // one homozygous effect. For additive traits this is simply a dominance of 0.5; for multiplicative traits + // it is a dominance of h = (sqrt(1+s)−1)/s, which is not 0.5 but converges to 0.5 as s approaches zero. + // SLiM does not attempt to infer that independent dominance is present; instead it must be explicitly set + // with a special dominance value of NAN, for a mutation or (as a default dominance) for a mutation type. + // When independent dominance is present, it can greatly speed up trait calculations because the effect of + // a whole mutation run on an independent-dominance trait can be calculated, cached, and used in all of the + // individuals that share that mutation run. This is the purpose of the independent-dominance caches. + // + // This caching is performed on a per-trait basis. If all mutations are independent-dominance for a given + // trait, and if there are no mutationEffect() callbacks that affect that trait (even by making it neutral), + // then an independent-dominance cache is set up for that trait for every mutation run in the species. This + // is done based upon the non-neutral mutation buffers set up previously; this mechanism depends upon that + // facility. The independent-dominance cache for a trait summarizes the contents of the nonneutral mutation + // buffer, allowing trait calculations for a given mutation run to skip the use of its buffer in favor of + // a single precalculated value. + // + // This is complicated slightly by the ploidy of particular chromosomes. The description above applies to + // mutation runs that represent diploid chromosomes. For haploid chromosomes, effects are "independent" by + // definition; ploidy is not a factor. For such mutation runs, when mutationEffect() callbacks are not + // present for a given trait (even neutral ones), the non-neutral mutation buffers are not set up at all; + // instead, the independent-dominance caches are set up directly from the main mutation buffer of each + // mutation run, and all trait calculations are based simply upon those caches (making haploid models very + // fast). FIXME MULTITRAIT this scheme is not set up yet; right now it makes a non-neutral cache first + // + // For intrinsically diploid chromosomes that can be present in one copy in a given individual (like an X), + // it is also a bit complicated since mutations can be hemizygous, which involves a different effect size + // than the independent-dominance effect size. For such chromosomes, independent-dominance caches are still + // set up (if the preconditions are met) and are used when the chromosome is represented by two non-null + // haplosomes in an individual (like an X in a female). When one haplosome is null, the cached values + // for independent dominance are not used; instead the effects are calculated from the non-neutral mutation + // buffer directly, to get the correct hemizygous effects. FIXME MULTITRAIT: Maybe we can be smarter here... + + // BCH 1/19/2025: PLANNED CHANGES: + // + // - I need to figure out what it means for nonneutral_cache_ to be nullptr. There are three useful meanings + // for this that I see: (a) the non-neutral cache is simply uninitialized/invalid, and will be allocated + // when it is time to set it up; (b) the non-neutral cache is in a valid state representing the fact that + // there are no nonneutral mutations; the cache is simply empty, equivalent to count_ == 0; or (c) the + // non-neutral cache is in a valid state representing the fact that ALL mutations are nonneutral, and so + // the non-neutral cache would contain identical data to the main mutation buffer for the mutation run. + // Independent dominance interacts with this; for (c) we would probably really want to have an allocated + // NonNeutralCache struct, but with zero capacity and zero mutations, with a special flag set somewhere + // so we know that this means state (c). With the above planned change we should have space to add such + // a flag, or we could use a special capacity value of -1 or something if we need to wedge it in. For + // (a) and (b) maybe both can share the state. Prior to non-neutral cache validation, a nullptr value + // can represent either state. After non-neutral cache validation, all caches have been validated and + // so state (a) will no longer exist; we will allocate a NonNeutralCache struct for every mutation run + // UNLESS we determine that we are in state (b), in which case we leave it unallocated to represent (b). + // This determination will be made again each time we validate; if the model stays neutral the pointer + // will remain nullptr. If, at any point, the validation process determines non-neutrality allocation + // will occur. If the model goes back to being neutral, deallocation will not be done; the allocated + // state is "sticky" to avoid allocation thrash, since we don't expect a non-neutral model to revert to + // neutrality permanently. + + // This struct represents the entire non-neutral cache, which can't entirely be described with a C struct due + // to variable-length elements. First are the capacity and count for the nonneutral mutation buffer. Then + // come slim_effect_t entries, one per trait in the species, for the independent-dominance cache values for + // all of the mutations in the mutation run. After that is the nonneutral mutation buffer itself: a vector + // of MutationIndex for all of the mutations in the nonneutral cache. This structure should be considered + // very private, and be accessed directly only in a few key places inside MutationRun. Note that MutationRun + // doesn't know the number of traits, and so it doesn't know the layout of this struct! In APIs where the + // layout of this struct needs to be known, a species_trait_count value is passed in from outside. This is + // kind of weird, but it avoids wasting a ton of storage (and time) on duplicated information. + typedef struct _NonNeutralCache { + mutable int32_t capacity_; // the capacity of the nonneutral mutation buffer + mutable int32_t count_; // the number of entries currently used; -1 indicates an invalid cache + slim_effect_t independent_dominance_cache_[]; // one independent-dominance summary per trait in the species + // the non-neutral MutationIndex buffer begins after the last per-trait entry in independent_dominance_cache_ + } NonNeutralCache; + + mutable NonNeutralCache *nonneutral_cache_ = nullptr; // OWNED POINTER: the contents of the nonneutal buffer, or nullptr #endif // SLIM_USE_NONNEUTRAL_CACHES() public: - mutable int64_t operation_id_ = 0; // used to mark the MutationRun objects that have been handled by a global operation - #if DEBUG mutable uint32_t use_count_CHECK_ = 0; // a checkback for use_count_ #endif - static inline slim_pedigreeid_t GetNextOperationID(void) - { - THREAD_SAFETY_IN_ACTIVE_PARALLEL("GetNextOperationID(): MutationRun::sOperationID change"); - - return ++(MutationRun::sOperationID); - } - // Allocation and disposal of MutationRun objects should go through these funnels. The point of this architecture // is to re-use the instances completely. We don't use EidosObjectPool here because it would construct/destruct the // objects, and we actually don't want that; we want the buffers in used MutationRun objects to stay allocated, for @@ -414,7 +472,8 @@ class MutationRun freed_run->mutation_count_ = 0; // empty the mutation buffer #if SLIM_USE_NONNEUTRAL_CACHES() - freed_run->nonneutral_mutations_count_ = -1; // mark the non-neutral mutation cache as invalid + if (freed_run->nonneutral_cache_) + freed_run->nonneutral_cache_->count_ = -1; // mark the non-neutral mutation cache as invalid #endif // add our new run to the free pool @@ -485,7 +544,8 @@ class MutationRun inline __attribute__((always_inline)) void will_modify_run(void) { #if SLIM_USE_NONNEUTRAL_CACHES() - nonneutral_mutations_count_ = -1; // invalidate the nonneutral cache since the run is changing + if (nonneutral_cache_) + nonneutral_cache_->count_ = -1; // invalidate the nonneutral cache since the run is changing #endif } @@ -606,48 +666,6 @@ class MutationRun *sort_position = p_mutation_index; } - /* - This version of insert_sorted_mutation() searches for the insertion point from the end - instead of the beginning. I investigated that, but ultimately decided on a different - course of action, and this change seems unnecessary and destabilizing; I don't think - it would benefit any of the users of this method, on average. Keeping the code for - posterity. BCH 29 October 2017 - - inline void insert_sorted_mutation(MutationIndex p_mutation_index) - { - // first push it back on the end, which deals with capacity/locking issues - emplace_back(p_mutation_index); - - // if it was our first element, then we're done; this would work anyway, but since it is extremely common let's short-circuit it - if (mutation_count_ == 1) - return; - - // then find the proper position for it; mutations are often added in ascending order, so let's search backwards - Mutation *mut_ptr_to_insert = gSLiM_Mutation_Block + p_mutation_index; - MutationIndex *sort_position = end_pointer() - 2; // the position back one from the newly added element (at end-1) - const MutationIndex *end_position = begin_pointer_const() - 1; // the position back one from the start of the mutation run - - // check for the mutation actually belonging at the end, for the quick return and simple completion design - if (!CompareMutations(mut_ptr_to_insert, gSLiM_Mutation_Block + *sort_position)) // if (p_mutation->position_ >= (*sort_position)->position_) - return; - - // ok, it doesn't belong at the end; start searching at end_pointer() - 3, which might not exist (might ==end_position) - --sort_position; - - for ( ; sort_position != end_position; --sort_position) - if (!CompareMutations(mut_ptr_to_insert, gSLiM_Mutation_Block + *sort_position)) // if (p_mutation->position_ >= (*sort_position)->position_) - break; - - // ok, it belongs right *after* sort_position; the mutation at sort_position should stay, so skip ahead one - ++sort_position; - - // the new mutation has a position less than that at sort_position, so we need to move everybody upward - memmove(sort_position + 1, sort_position, (char *)(end_pointer_const() - 1) - (char *)sort_position); - - // finally, put the mutation where it belongs - *sort_position = p_mutation_index; - }*/ - inline void insert_sorted_mutation_if_unique(Mutation *p_mut_block_ptr, MutationIndex p_mutation_index) { // first push it back on the end, which deals with capacity/locking issues @@ -760,7 +778,7 @@ class MutationRun } void _RemoveFixedMutations(Mutation *p_mut_block_ptr); - inline __attribute__((always_inline)) void RemoveFixedMutations(Mutation *p_mut_block_ptr, int64_t p_operation_id) + inline __attribute__((always_inline)) void RemoveFixedMutations(Mutation *p_mut_block_ptr, slim_operation_id_t p_operation_id) { if (operation_id_ != p_operation_id) { @@ -803,58 +821,72 @@ class MutationRun // splitting mutation runs void split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_half, MutationRun **p_second_half, slim_position_t p_split_first_position, MutationRunContext &p_mutrun_context) const; + + // + // Non-neutral mutation caching + // + #if SLIM_USE_NONNEUTRAL_CACHES() - // caching non-neutral mutations; see above for comments about how this works // note this method does NOT check external invalidation flags! it tells you only if the mutrun itself knows it is invalid! - inline __attribute__((always_inline)) bool nonneutral_cache_invalid(void) const { return (nonneutral_mutations_count_ == -1); } + inline __attribute__((always_inline)) bool nonneutral_cache_invalid(void) const { return (!nonneutral_cache_ || (nonneutral_cache_->count_ == -1)); } - inline __attribute__((always_inline)) void zero_out_nonneutral_buffer(void) const + inline __attribute__((always_inline)) MutationIndex *nonneutral_mutation_buffer(slim_trait_index_t species_trait_count) const { - if (!nonneutral_mutations_) + return (MutationIndex *)(nonneutral_cache_->independent_dominance_cache_ + species_trait_count); + } + + inline __attribute__((always_inline)) void zero_out_nonneutral_cache(slim_trait_index_t species_trait_count) const + { + if (!nonneutral_cache_) { - // If we don't have a buffer allocated yet, follow the same rules as for the main mutation buffer - nonneutral_mutation_capacity_ = SLIM_MUTRUN_INITIAL_CAPACITY; - nonneutral_mutations_ = (MutationIndex *)malloc(nonneutral_mutation_capacity_ * sizeof(MutationIndex)); - if (!nonneutral_mutations_) - EIDOS_TERMINATION << "ERROR (MutationRun::zero_out_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + // If we don't have a cache allocated yet, create a buffer with space for all the cache components + size_t total_size = sizeof(NonNeutralCache) + + species_trait_count * sizeof(slim_effect_t) + + SLIM_MUTRUN_INITIAL_CAPACITY * sizeof(MutationIndex); + + nonneutral_cache_ = (NonNeutralCache *)malloc(total_size); + if (!nonneutral_cache_) + EIDOS_TERMINATION << "ERROR (MutationRun::zero_out_nonneutral_cache): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); + + nonneutral_cache_->capacity_ = SLIM_MUTRUN_INITIAL_CAPACITY; } // empty out the current buffer contents - nonneutral_mutations_count_ = 0; + nonneutral_cache_->count_ = 0; } - inline __attribute__((always_inline)) void add_to_nonneutral_buffer(MutationIndex p_mutation_index) const + inline __attribute__((always_inline)) void expand_nonneutral_buffer(slim_trait_index_t species_trait_count) const { - // This is basically the emplace_back() code, but for the nonneutral buffer - if (nonneutral_mutations_count_ == nonneutral_mutation_capacity_) - { #ifdef __clang_analyzer__ - assert(nonneutral_mutation_capacity_ > 0); + assert(nonneutral_cache_->capacity_ > 0); #endif - - if (nonneutral_mutation_capacity_ < 32) - nonneutral_mutation_capacity_ <<= 1; // double the number of pointers we can hold - else - nonneutral_mutation_capacity_ += 16; - - nonneutral_mutations_ = (MutationIndex *)realloc(nonneutral_mutations_, nonneutral_mutation_capacity_ * sizeof(MutationIndex)); - if (!nonneutral_mutations_) - EIDOS_TERMINATION << "ERROR (MutationRun::add_to_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - } - *(nonneutral_mutations_ + nonneutral_mutations_count_) = p_mutation_index; - ++nonneutral_mutations_count_; + // we don't just double ad infinitum, because we don't want to use an inordinate amount of memory + // adding only 32 capacity at a time is a bit slow, but once we've grown to the high-water size + // we should stabilize and not have to realloc any more, so perhaps it's worthwhile... + if (nonneutral_cache_->capacity_ < 128) + nonneutral_cache_->capacity_ <<= 1; // double the number of mutations we can hold + else + nonneutral_cache_->capacity_ += 32; + + size_t total_size = sizeof(NonNeutralCache) + + species_trait_count * sizeof(slim_effect_t) + + nonneutral_cache_->capacity_ * sizeof(MutationIndex); + + nonneutral_cache_ = (NonNeutralCache *)realloc(nonneutral_cache_, total_size); + if (!nonneutral_cache_) + EIDOS_TERMINATION << "ERROR (MutationRun::expand_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); } - void cache_nonneutral_mutations_REGIME_0(void) const; - void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr) const; - void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr) const; - void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr) const; + void cache_nonneutral_mutations_REGIME_0(slim_trait_index_t species_trait_count) const; + void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const; + void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const; + void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const; void check_nonneutral_mutation_cache() const; - inline __attribute__((always_inline)) void beginend_nonneutral_pointers(const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max) const + inline __attribute__((always_inline)) void beginend_nonneutral_pointers(const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, slim_trait_index_t species_trait_count) const { #if DEBUG // All nonneutral caches must be validated ahead of time; see Species::ValidateNonNeutralCaches() @@ -862,8 +894,10 @@ class MutationRun #endif // Return the requested pointers to allow the caller to iterate over the nonneutral mutation buffer - *p_mutptr_iter = nonneutral_mutations_; - *p_mutptr_max = nonneutral_mutations_ + nonneutral_mutations_count_; + MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + + *p_mutptr_iter = mutation_buffer; + *p_mutptr_max = mutation_buffer + nonneutral_cache_->count_; } #if SLIM_PROFILE_NONNEUTRAL_CACHES() @@ -872,16 +906,33 @@ class MutationRun { *p_mutation_count += mutation_count_; - if (nonneutral_mutations_count_ != -1) - *p_nonneutral_count += nonneutral_mutations_count_; + if (nonneutral_cache_->count_ != -1) + *p_nonneutral_count += nonneutral_cache_->count_; } #endif // SLIM_PROFILE_NONNEUTRAL_CACHES() + + // + // Independent-dominance caches + // + +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + + template + void validate_independent_dominance_cache_for_trait(slim_trait_index_t trait_index, MutationBlock *mutation_block) const; + + inline __attribute__((always_inline)) slim_effect_t independent_dominance_cache_for_trait(slim_trait_index_t trait_index) const + { + return nonneutral_cache_->independent_dominance_cache_[trait_index]; + } + +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + #endif // SLIM_USE_NONNEUTRAL_CACHES() // Memory usage tallying, for outputUsage() size_t MemoryUsageForMutationIndexBuffers(void) const; - size_t MemoryUsageForNonneutralCaches(void) const; + size_t MemoryUsageForNonneutralCaches(slim_trait_index_t trait_count) const; }; // We need MutationType below, but we can't include it at top because it requires MutationRun to be defined... diff --git a/core/population.cpp b/core/population.cpp index f3648cd1..afc02245 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5441,7 +5441,7 @@ void Population::UniqueMutationRuns(void) std::clock_t begin = std::clock(); #endif int64_t total_mutruns = 0, total_hash_collisions = 0, total_identical = 0, total_uniqued_away = 0, total_preexisting = 0, total_final = 0; - int64_t operation_id = MutationRun::GetNextOperationID(); + slim_operation_id_t operation_id = MutationRun::GetNextOperationID(); const std::vector &chromosomes = species_.Chromosomes(); size_t chromosome_count = chromosomes.size(); @@ -6110,7 +6110,7 @@ void Population::AssessMutationRuns(void) slim_position_t mutrun_length = 0; int64_t mutation_total = 0; - int64_t operation_id = MutationRun::GetNextOperationID(); + slim_operation_id_t operation_id = MutationRun::GetNextOperationID(); for (const std::pair &subpop_pair : subpops_) { @@ -7616,7 +7616,7 @@ void Population::RemoveAllFixedMutations(void) // We remove fixed mutations from each MutationRun just once; this is the operation ID we use for that int first_haplosome_index = species_.FirstHaplosomeIndices()[chromosome_index]; int last_haplosome_index = species_.LastHaplosomeIndices()[chromosome_index]; - int64_t operation_id = MutationRun::GetNextOperationID(); + slim_operation_id_t operation_id = MutationRun::GetNextOperationID(); for (std::pair &subpop_pair : subpops_) // subpopulations { diff --git a/core/slim_globals.h b/core/slim_globals.h index 0cee1e18..cfe43041 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -125,6 +125,7 @@ typedef int64_t slim_pedigreeid_t; // identifiers for pedigreed individuals; ov typedef int64_t slim_haplosomeid_t; // identifiers for pedigreed haplosomes; not user-visible, used by the tree-recording code, pedigree_id*2 + [0/1] typedef int32_t slim_polymorphismid_t; // identifiers for polymorphisms, which need only 32 bits since they are only segregating mutations typedef int32_t slim_trait_index_t; // indices for traits; we are limited to 256 traits by SLIM_MAX_TRAITS at present, so this is plenty of room +typedef uint32_t slim_operation_id_t; // used for MutationRun's operation_id_, as a unique identifier of a given task being worked upon typedef float slim_effect_t; // storage of trait effects (e.g., selection coefficients) in memory-tight classes; also dominance coefficients typedef float slim_fitness_t; // storage of fitness effects (e.g., fitnessScaling values) and final individual fitness values diff --git a/core/species.cpp b/core/species.cpp index caa0ab28..e2abc3d4 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -455,15 +455,20 @@ void Species::PrepareForTraitCalculations(std::vector &mutation const std::vector &traits = Traits(); TraitCalculationRegime last_trait_calculation_regime = current_trait_calculation_regime_; TraitCalculationRegime new_trait_calculation_regime = TraitCalculationRegime::kUndefined; + std::vector pure_independent_dominance_traits; - // First, we want to save off the old values of our flags that govern nonneutral caching - for (auto muttype_iter : mut_types) + // First, save off the old values of flags that influence nonneutral caching. We don't do this if we're + // starting afresh; we will recache completely anyway, and we need to avoid reading uninitialized values. + if (last_trait_calculation_regime != TraitCalculationRegime::kUndefined) { - MutationType *muttype = muttype_iter.second; - - muttype->previous_is_pure_neutral_now_ = muttype->is_pure_neutral_now_; - muttype->previous_subject_to_mutationEffect_callback_ = muttype->subject_to_mutationEffect_callback_; - muttype->previous_subject_to_non_neutral_callback_ = muttype->subject_to_non_neutral_callback_; + for (auto muttype_iter : mut_types) + { + MutationType *muttype = muttype_iter.second; + + muttype->previous_is_pure_neutral_now_ = muttype->is_pure_neutral_now_; + muttype->previous_subject_to_mutationEffect_callback_ = muttype->subject_to_mutationEffect_callback_; + muttype->previous_subject_to_non_neutral_callback_ = muttype->subject_to_non_neutral_callback_; + } } // Initially, every mutation type is assumed to be uninfluenced by callbacks, and thus pure neutral @@ -478,10 +483,14 @@ void Species::PrepareForTraitCalculations(std::vector &mutation } // Similarly, every trait is assumed to be uninfluenced by callbacks, and thus pure neutral if all - // mutations' effects are intrinsically neutral for that trait (i.e., have an effect of 0.0) + // mutations' effects are intrinsically neutral for that trait (i.e., have an effect of 0.0). For + // traits we also determine pure independent dominance; similarly, every trait is assumed to be + // uninfluenced by callbacks, and thus pure independent dominance if all mutations' effects exhibit + // pure independent dominance for that trait (i.e., have a dominance coefficient of NAN). for (Trait *trait : traits) { - trait->is_pure_neutral_now = trait->trait_all_neutral_mutations_; + trait->is_pure_neutral_now_ = trait->trait_all_neutral_mutations_; + trait->is_pure_independent_dominance_now_ = trait->trait_all_mutations_independent_dominance_ && !trait->trait_all_neutral_mutations_; trait->subject_to_mutationEffect_callback_ = false; trait->subject_to_non_neutral_callback_ = false; } @@ -532,15 +541,26 @@ void Species::PrepareForTraitCalculations(std::vector &mutation bool makes_neutral = _CallbackMakesTraitNeutral(mutationEffect_callback, callback_trait); // callbacks are always specific to one mutation type, so unless there is only one mutation type - // defined, we can't easily infer the the trait has been made entirely neutral; we can just infer + // defined, we can't easily infer that the trait has been made entirely neutral; we can just infer // that it might make a previously neutral trait non-neutral. + + // note that ANY callback that applies to a given trait turns off pure-independent-dominance, + // even if the callback is neutral; this is because the independent-dominance mechanism is + // based on the nonneutral mutation buffer, without the involvement of callbacks, so even a + // neutral or global-neutral callback causes incorrect independent dominance values to be cached. + // we could instead leave is_pure_independent_dominance_now_ unaffected here, and turn off the + // independent dominance mechanism when actually using the cached values if any callback is + // present; that would work, but then we'd often spend time making the independent dominance + // cache and then not using it at all; ideally we'd be smarter about this. FIXME MULTICHROM + if (mut_types.size() == 1) { if (callback_trait) { callback_trait->subject_to_mutationEffect_callback_ = true; callback_trait->subject_to_non_neutral_callback_ = !makes_neutral; - callback_trait->is_pure_neutral_now = makes_neutral; + callback_trait->is_pure_neutral_now_ = makes_neutral; + callback_trait->is_pure_independent_dominance_now_ = false; } else { @@ -549,19 +569,19 @@ void Species::PrepareForTraitCalculations(std::vector &mutation { affectedTrait->subject_to_mutationEffect_callback_ = true; affectedTrait->subject_to_non_neutral_callback_ = !makes_neutral; - affectedTrait->is_pure_neutral_now = makes_neutral; + affectedTrait->is_pure_neutral_now_ = makes_neutral; + affectedTrait->is_pure_independent_dominance_now_ = false; } } } else if (!makes_neutral) { - // with more than one muttype, callbacks only make traits non-neutral; we don't try to track the - // possibility that multiple callbacks might together render a trait neutral again if (callback_trait) { callback_trait->subject_to_mutationEffect_callback_ = true; callback_trait->subject_to_non_neutral_callback_ = true; - callback_trait->is_pure_neutral_now = false; + callback_trait->is_pure_neutral_now_ = false; + callback_trait->is_pure_independent_dominance_now_ = false; } else { @@ -570,10 +590,26 @@ void Species::PrepareForTraitCalculations(std::vector &mutation { affectedTrait->subject_to_mutationEffect_callback_ = true; affectedTrait->subject_to_non_neutral_callback_ = true; - affectedTrait->is_pure_neutral_now = false; + affectedTrait->is_pure_neutral_now_ = false; + affectedTrait->is_pure_independent_dominance_now_ = false; } } } + else // if (makes_neutral) + { + // with more than one muttype, callbacks only make traits non-neutral; we don't try to track the + // possibility that multiple callbacks might together render a trait neutral again. as above, + // a callback never changes a non-independent-dominance trait into an independent-dominance trait. + if (callback_trait) + { + callback_trait->is_pure_independent_dominance_now_ = false; + } + else + { + for (Trait *affectedTrait : traits) + affectedTrait->is_pure_independent_dominance_now_ = false; + } + } } // Now we would like to know if there is any callback that we actually need to call; if every callback @@ -663,6 +699,20 @@ void Species::PrepareForTraitCalculations(std::vector &mutation if (new_trait_calculation_regime == TraitCalculationRegime::kUndefined) EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) nonneutral regime was not decided." << EidosTerminate(); + if (new_trait_calculation_regime != TraitCalculationRegime::kPureNeutral) + { + // Having determined which traits, if any, exhibit pure independent dominance, we make a vector of them. + // This does not affect which mutations are placed into the non-neutral cache, because mutations may be + // non-neutral and non-independent-dominance for other traits, and because we need the non-neutral cache + // for the hemizygous case anyway. It does mean that mutations in the non-neutral cache will additionally + // be summarized for their independent-dominance effects, after the non-neutral cache itself is built. + // We do not do this if we are in the "pure neutral" regime, since genetic calculations are not needed. + // Similarly, we do not do it for any traits that are themselves "pure neutral". + for (Trait *trait : traits) + if (trait->is_pure_independent_dominance_now_) + pure_independent_dominance_traits.push_back(trait->Index()); + } + #if DEBUG_TRAIT_DEMAND() std::cout << "# " << community_.Tick() << " +++ PrepareForTraitCalculations() old regime " << current_trait_calculation_regime_ << ", new regime " << new_trait_calculation_regime << std::endl; #endif @@ -671,7 +721,21 @@ void Species::PrepareForTraitCalculations(std::vector &mutation current_trait_calculation_regime_ = new_trait_calculation_regime; #if SLIM_USE_NONNEUTRAL_CACHES() - ValidateNonNeutralCaches(last_trait_calculation_regime); +#if DEBUG_TRAIT_DEMAND() + if (pure_independent_dominance_traits.size()) + { + std::cout << "# " << community_.Tick() << " +++ making independent dominance caches for traits {"; + for (slim_trait_index_t trait_index : pure_independent_dominance_traits) + std::cout << " " << Traits()[trait_index]->Name(); + std::cout << " }" << std::endl; + } + else + { + std::cout << "# " << community_.Tick() << " +++ making independent dominance caches for NO traits" << std::endl; + } +#endif + + _ValidateNonNeutralCaches(last_trait_calculation_regime, pure_independent_dominance_traits); #endif } @@ -798,7 +862,7 @@ bool Species::_CallbackMakesTraitNeutral(SLiMEidosBlock *mutationEffect_callback } #if SLIM_USE_NONNEUTRAL_CACHES() -void Species::ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calculation_regime) +void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calculation_regime, std::vector &pure_independent_dominance_traits) { const std::map &mut_types = MutationTypes(); @@ -810,7 +874,8 @@ void Species::ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcula // very criteria upon which the existing caches were built has changed. See mutation_run.h. if (current_trait_calculation_regime_ != last_trait_calculation_regime) { - // Changing from one regime to another demands a full recache, by definition. + // Changing from one regime to another demands a full recache, by definition. This implies that if + // last_trait_calculation_regime is TraitCalculationRegime::kUndefined we always recache. all_nonneutral_caches_invalid_ = true; } else if (current_trait_calculation_regime_ == TraitCalculationRegime::kPureNeutral) @@ -872,8 +937,16 @@ void Species::ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcula for (Chromosome *chromosome : chromosomes_) { + // FIXME MULTITRAIT: right now these optimization flags are the same across all chromosomes, but they could potentially be calculated per-chromosome bool all_nonneutral_caches_invalid_for_chromosome = all_nonneutral_caches_invalid_; TraitCalculationRegime trait_calculation_regime_for_chromosome = current_trait_calculation_regime_; + bool independent_dominance_present_for_chromosome = pure_independent_dominance_traits.size(); + bool f_haploid_chromosome = true; + + if ((chromosome->Type() == ChromosomeType::kA_DiploidAutosome) || + (chromosome->Type() == ChromosomeType::kX_XSexChromosome) || + (chromosome->Type() == ChromosomeType::kZ_ZSexChromosome)) + f_haploid_chromosome = false; #if SLIM_PROFILE_NONNEUTRAL_CACHES() // PROFILING @@ -890,35 +963,124 @@ void Species::ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcula MutationRunContext &mutrun_context = chromosome->ChromosomeMutationRunContextForThread(mutrun_context_index); MutationRunPool &mutrun_pool = mutrun_context.in_use_pool_; - int64_t (Species::*_ValidateNonNeutralCachesForMutationRunPool_TEMPLATED)(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr) = nullptr; - - if (all_nonneutral_caches_invalid_for_chromosome) { - switch (trait_calculation_regime_for_chromosome) { - case TraitCalculationRegime::kPureNeutral: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kNoActiveCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kNonNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + int64_t (Species::*_ValidateNonNeutralCachesForMutationRunPool_TEMPLATED)(MutationRunPool &, Mutation *, std::vector &) = nullptr; + + if (f_haploid_chromosome) + { + if (independent_dominance_present_for_chromosome) + { + if (all_nonneutral_caches_invalid_for_chromosome) { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } else { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } + } else { + if (all_nonneutral_caches_invalid_for_chromosome) { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } else { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } } - } else { - switch (trait_calculation_regime_for_chromosome) { - case TraitCalculationRegime::kPureNeutral: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kNoActiveCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kNonNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + else + { + if (independent_dominance_present_for_chromosome) + { + if (all_nonneutral_caches_invalid_for_chromosome) { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } else { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } + } else { + if (all_nonneutral_caches_invalid_for_chromosome) { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } else { + switch (trait_calculation_regime_for_chromosome) { + case TraitCalculationRegime::kPureNeutral: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNoActiveCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kNonNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); + } + } } } - __attribute__ ((unused)) int64_t this_recached_count = (this->*(_ValidateNonNeutralCachesForMutationRunPool_TEMPLATED))(mutrun_pool, mut_block_ptr); + __attribute__ ((unused)) int64_t this_recached_count = (this->*(_ValidateNonNeutralCachesForMutationRunPool_TEMPLATED))(mutrun_pool, mut_block_ptr, pure_independent_dominance_traits); #if SLIM_PROFILE_NONNEUTRAL_CACHES() // PROFILING @@ -936,75 +1098,111 @@ void Species::ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcula all_nonneutral_caches_invalid_ = false; } -template -int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr) +template +int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr, std::vector &pure_independent_dominance_traits) { - // This applies the specified nonneutral cache regime to all mutation runs in p_mutrun_pool. It's templated - // for efficiency, which might be overkill right now, but I do expect the code complexity here to increase. +#if DEBUG + if (f_independent_dominance_present != (pure_independent_dominance_traits.size() > 0)) + EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) f_independent_dominance_present incorrect." << EidosTerminate(); +#endif + + // This applies the specified nonneutral cache regime to all mutation runs in p_mutrun_pool, producing + // nonneutral mutation caches and then optionally computing cached independent dominance summaries. size_t mutrun_count = p_mutrun_pool.size(); const MutationRun **mutrun_pointers = p_mutrun_pool.data(); + slim_trait_index_t species_trait_count = TraitCount(); - if (f_all_caches_for_pool_invalid) +#if (SLIMPROFILING == 1) + int64_t recached_count; + + if (!f_all_caches_for_pool_invalid) + recached_count = 0; +#endif + + for (size_t mutrun_index = 0; mutrun_index < mutrun_count; ++mutrun_index) { - for (size_t mutrun_index = 0; mutrun_index < mutrun_count; ++mutrun_index) + const MutationRun *mutrun = mutrun_pointers[mutrun_index]; + + if (f_all_caches_for_pool_invalid || mutrun->nonneutral_cache_invalid()) { - const MutationRun *mutrun = mutrun_pointers[mutrun_index]; - switch (f_nonneutral_cache_regime) { - case TraitCalculationRegime::kPureNeutral: mutrun->cache_nonneutral_mutations_REGIME_0(); break; - case TraitCalculationRegime::kNoActiveCallbacks: mutrun->cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr); break; - case TraitCalculationRegime::kAllNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr); break; - case TraitCalculationRegime::kNonNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr); break; + case TraitCalculationRegime::kPureNeutral: mutrun->cache_nonneutral_mutations_REGIME_0(species_trait_count); break; + case TraitCalculationRegime::kNoActiveCallbacks: mutrun->cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr, species_trait_count); break; + case TraitCalculationRegime::kAllNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr, species_trait_count); break; + case TraitCalculationRegime::kNonNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr, species_trait_count); break; default: EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) unrecognized regime." << EidosTerminate(); } - } - -#if (SLIMPROFILING == 1) - return mutrun_count; -#endif - } - else - { + #if (SLIMPROFILING == 1) - int64_t recached_count = 0; + if (!f_all_caches_for_pool_invalid) + recached_count++; #endif - for (size_t mutrun_index = 0; mutrun_index < mutrun_count; ++mutrun_index) - { - const MutationRun *mutrun = mutrun_pointers[mutrun_index]; - if (mutrun->nonneutral_cache_invalid()) +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + if (f_independent_dominance_present) { - switch (f_nonneutral_cache_regime) + // Now we calculate cached values for pure independent dominance traits. Note that the existence of these traits does not change + // our non-neutral caching behavior in any way; this mechanism simply piggybacks on top of the non-neutral mutation buffer. For + // independent dominance traits, the values we cache here will be used to calculate individual trait values in most cases (but not + // for individuals that are hemizygous for the mutation run; hemizygous mutations have a different dominance and are not cached here). + // We do this caching immediately after non-neutral caching for each mutrun, in the hopes of getting some memory locality benefits. + for (slim_trait_index_t independent_dominance_trait_index : pure_independent_dominance_traits) { - case TraitCalculationRegime::kPureNeutral: mutrun->cache_nonneutral_mutations_REGIME_0(); break; - case TraitCalculationRegime::kNoActiveCallbacks: mutrun->cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr); break; - case TraitCalculationRegime::kAllNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr); break; - case TraitCalculationRegime::kNonNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr); break; - default: EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) unrecognized regime." << EidosTerminate(); + Trait *independent_dominance_trait = traits_[independent_dominance_trait_index]; + + if (independent_dominance_trait->Type() == TraitType::kAdditive) + mutrun->validate_independent_dominance_cache_for_trait(independent_dominance_trait_index, mutation_block_); + else + mutrun->validate_independent_dominance_cache_for_trait(independent_dominance_trait_index, mutation_block_); } - -#if (SLIMPROFILING == 1) - recached_count++; -#endif } +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() } + } + #if (SLIMPROFILING == 1) + if (f_all_caches_for_pool_invalid) + return mutrun_count; + else return recached_count; #endif - } return 0; // when not profiling, we don't count the number of mutation runs recached } -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); #endif // SLIM_USE_NONNEUTRAL_CACHES() @@ -5091,7 +5289,7 @@ void Species::TabulateSLiMMemoryUsage_Species(SLiMMemoryUsage_Species *p_usage) { mutrun_objectCount++; mutrun_externalBuffers += inuse_mutrun->MemoryUsageForMutationIndexBuffers(); - mutrun_nonneutralCaches += inuse_mutrun->MemoryUsageForNonneutralCaches(); + mutrun_nonneutralCaches += inuse_mutrun->MemoryUsageForNonneutralCaches(TraitCount()); } } } @@ -5118,7 +5316,7 @@ void Species::TabulateSLiMMemoryUsage_Species(SLiMMemoryUsage_Species *p_usage) { mutrun_unusedCount++; mutrun_unusedBuffers += free_mutrun->MemoryUsageForMutationIndexBuffers(); - mutrun_unusedBuffers += free_mutrun->MemoryUsageForNonneutralCaches(); + mutrun_unusedBuffers += free_mutrun->MemoryUsageForNonneutralCaches(TraitCount()); } } } @@ -5327,7 +5525,7 @@ void Species::CollectMutationProfileInfo(void) profile_max_mutation_index_ = std::max(profile_max_mutation_index_, (int64_t)registry_size); // tally per-chromosome information - int64_t operation_id = MutationRun::GetNextOperationID(); + slim_operation_id_t operation_id = MutationRun::GetNextOperationID(); for (Chromosome *chromosome : Chromosomes()) { diff --git a/core/species.h b/core/species.h index 5d139dd2..67fe930f 100644 --- a/core/species.h +++ b/core/species.h @@ -474,17 +474,21 @@ class Species : public EidosDictionaryUnretained #endif } + // PrepareForTraitCalculations() is the funnel method to be called before using trait values, for example to + // calculate individual fitness. It determines the trait calculation regime (see TraitCalculationRegime), + // invalidates caches as needed, then validates nonneutral and independent dominance caches as needed. It is + // called by demandPhenotype(), demandPhenotypeForIndividuals(), and RecalculateFitness(). void PrepareForTraitCalculations(std::vector &mutationEffect_callbacks); bool _CallbackMakesMutationTypeNeutral(SLiMEidosBlock *mutationEffect_callback, MutationType *&mut_type_ptr_ref); bool _CallbackMakesTraitNeutral(SLiMEidosBlock *mutationEffect_callback, Trait *&trait_ptr_ref); #if SLIM_USE_NONNEUTRAL_CACHES() - // Validates the MutationRun non-neutral caches across the species. This must be called immediately before nonneutral cache use, every time. - // Note that it does not call mutationEffect() callbacks; it just needs to examine them to determine the status of each MutationType. - void ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calculation_regime); + // Validates the MutationRun nonneutral caches across the species. Called by PrepareForTraitCalculations(). + void _ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calculation_regime, std::vector &pure_independent_dominance_traits); - template - int64_t _ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr); + // Validates nonneutral caches for mutation runs in one MutationRunPool. Called by _ValidateNonNeutralCaches(). + template + int64_t _ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr, std::vector &pure_independent_dominance_traits); #endif // Chromosome configuration and access diff --git a/core/trait.h b/core/trait.h index a8681854..2b588e17 100644 --- a/core/trait.h +++ b/core/trait.h @@ -89,12 +89,23 @@ class Trait : public EidosDictionaryRetained // Optimization flags set up by Species::PrepareForTraitCalculations() and valid only subsequent to that call. - // If set, it indicates that the trait is currently completely neutral, including callbacks – either because + // If set, this flag indicates that the trait is currently completely neutral, including callbacks - either // because trait_all_neutral_mutations_ is set and the trait cannot be influenced by any callbacks in the // current subpopulation / tick, or because an active callback actually sets this trait to be neutral in // this subpopulation / tick. Traits for which this flag is set can be safely elided from trait calculations // except for the baseline offset and individual offset. - mutable bool is_pure_neutral_now; + mutable bool is_pure_neutral_now_; + + // If set, this flag indicates that the trait currently exhibits independent dominance for all mutations, + // including the effects of callbacks - because trait_all_mutations_independent_dominance_ is set and the + // trait cannot be influenced by ANY callbacks in the current subpopulation / tick. The point is that the + // intrinsic effects of all mutations in the non-neutral cache on the trait must be reliable. (Even global- + // neutral callbacks turn off this optimization, because independent-dominance cached values are calculated + // based upon the mutations in the non-neutral cache, and even mutations made globally neutral for a given + // trait may be kept in the non-neutral cache for other reasons, such as effects on other traits.) Traits + // for which this flag is set can be calculated more efficiently, with cached per-mutation-run values. We + // avoid setting this flag to true when is_pure_neutral_now_ is true; being pure neutral takes precedence. + mutable bool is_pure_independent_dominance_now_; // If set, subject_to_mutationEffect_callback_ indicates that the trait is influenced by a mutationEffect // callback in at least one subpop. Traits with this flag set are subject to a callback, and so the effect From d866ffd70a5557eab7dd43f5e66c4cae1e961e37 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Mon, 19 Jan 2026 14:46:55 -0600 Subject: [PATCH 084/107] some template polish --- core/individual.cpp | 40 +-- core/population.cpp | 44 ++- core/species.cpp | 27 +- core/subpopulation.cpp | 648 ++++++++++++++++++++++++----------------- 4 files changed, 442 insertions(+), 317 deletions(-) diff --git a/core/individual.cpp b/core/individual.cpp index 6d5f2396..a5c50ff5 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -6543,15 +6543,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual // Next we cache method pointers for haploid and diploid chromosomes, which we will use throughout. These // are templated for efficiency, so we have to choose the correct template. That depends on the subpopulation - // since each subpopulation might have a different set of mutationEffect() callbacks. Note the template - // for _IncorporateEffects_Haploid() (which handles both haploid and hemizygous cases): - // - // template - // - // and the template for _IncorporateEffects_Diploid() (which handles the non-hemizygous diploid case): - // - // template < const bool f_additiveTrait, const bool f_callbacks, const bool f_singlecallback> - // + // since each subpopulation might have a different set of mutationEffect() callbacks. if (has_active_callbacks) { // If we have any active callbacks, we have to account for all callbacks (active or not), since // FIXME MULTITRAIT this is not true any more! @@ -7149,14 +7141,6 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s // Cache method pointers for haploid and diploid chromosomes for this trait. These are templated // for efficiency, so we have to choose the correct template. That depends on whether the trait is // additive or multiplicative; it could potentially also depend on per-trait callbacks (FIXME MULTITRAIT). - // Note the template for _IncorporateEffects_Haploid() (which handles both haploid and hemizygous cases): - // - // template - // - // and the template for _IncorporateEffects_Diploid() (which handles the non-hemizygous diploid case): - // - // template < const bool f_additiveTrait, const bool f_callbacks, const bool f_singlecallback> - // void (Individual::*IncorporateEffects_Haploid_TEMPLATED)(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) = nullptr; void (Individual::*IncorporateEffects_Hemizygous_TEMPLATED)(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) = nullptr; void (Individual::*IncorporateEffects_Diploid_TEMPLATED)(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks) = nullptr; @@ -7398,6 +7382,17 @@ template &p_mutationEffect_callbacks) { #if DEBUG + // Check template flags + if (f_additiveTrait != (trait->Type() == TraitType::kAdditive)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) f_additiveTrait flag is incorrect." << EidosTerminate(); + + size_t callback_count = p_mutationEffect_callbacks.size(); + + if (f_callbacks != (callback_count > 0)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) f_callbacks flag is incorrect." << EidosTerminate(); + if (f_singlecallback != (callback_count == 1)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) f_singlecallback flag is incorrect." << EidosTerminate(); + // This method assumes that haplosome is not a null haplosome; the caller needs to guarantee this if (haplosome->IsNull()) EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Haploid): (internal error) null haplosome." << EidosTerminate(); @@ -7501,6 +7496,17 @@ template &p_mutationEffect_callbacks) { #if DEBUG + // Check template flags + if (f_additiveTrait != (trait->Type() == TraitType::kAdditive)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) f_additiveTrait flag is incorrect." << EidosTerminate(); + + size_t callback_count = p_mutationEffect_callbacks.size(); + + if (f_callbacks != (callback_count > 0)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) f_callbacks flag is incorrect." << EidosTerminate(); + if (f_singlecallback != (callback_count == 1)) + EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) f_singlecallback flag is incorrect." << EidosTerminate(); + // This method assumes that haplosome1 and haplosome2 are not null; the caller needs to guarantee this if (haplosome1->IsNull() || haplosome2->IsNull()) EIDOS_TERMINATION << "ERROR (Individual::_IncorporateEffects_Diploid): (internal error) null haplosome." << EidosTerminate(); diff --git a/core/population.cpp b/core/population.cpp index afc02245..52ea44b9 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -2912,6 +2912,12 @@ template void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector *p_recombination_callbacks, std::vector *p_mutation_callbacks) { #if DEBUG + // Check template flags + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Population::HaplosomeCrossed): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + + // f_callbacks is sometimes over-broad, for complicated reasons, so we don't check it here + // This method is designed to run in parallel, but only if no callbacks are enabled if (p_recombination_callbacks || p_mutation_callbacks) THREAD_SAFETY_IN_ANY_PARALLEL("Population::HaplosomeCrossed(): recombination and mutation callbacks are not allowed when executing in parallel"); @@ -3765,16 +3771,22 @@ void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_h DoHeteroduplexRepair(heteroduplex, breakpoints_ptr, breakpoints_count, parent_haplosome_1, parent_haplosome_2, &p_child_haplosome); } -template void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector *p_recombination_callbacks, std::vector *p_mutation_callbacks); -template void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector *p_recombination_callbacks, std::vector *p_mutation_callbacks); -template void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector *p_recombination_callbacks, std::vector *p_mutation_callbacks); -template void Population::HaplosomeCrossed(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector *p_recombination_callbacks, std::vector *p_mutation_callbacks); +template void Population::HaplosomeCrossed(Chromosome &, Haplosome &, Haplosome *, Haplosome *, std::vector *, std::vector *); +template void Population::HaplosomeCrossed(Chromosome &, Haplosome &, Haplosome *, Haplosome *, std::vector *, std::vector *); +template void Population::HaplosomeCrossed(Chromosome &, Haplosome &, Haplosome *, Haplosome *, std::vector *, std::vector *); +template void Population::HaplosomeCrossed(Chromosome &, Haplosome &, Haplosome *, Haplosome *, std::vector *, std::vector *); // generate a child haplosome from parental haplosomes, clonally with mutation template void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome, std::vector *p_mutation_callbacks) { #if DEBUG + // Check template flags + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Population::HaplosomeCloned): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + + // f_callbacks is sometimes over-broad, for complicated reasons, so we don't check it here + // This method is designed to run in parallel, but only if no callbacks are enabled if (p_mutation_callbacks) THREAD_SAFETY_IN_ANY_PARALLEL("Population::HaplosomeCloned(): mutation callbacks are not allowed when executing in parallel"); @@ -4035,19 +4047,25 @@ void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_ha } } -template void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome, std::vector *p_mutation_callbacks); -template void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome, std::vector *p_mutation_callbacks); -template void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome, std::vector *p_mutation_callbacks); -template void Population::HaplosomeCloned(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome, std::vector *p_mutation_callbacks); +template void Population::HaplosomeCloned(Chromosome &, Haplosome &, Haplosome *, std::vector *); +template void Population::HaplosomeCloned(Chromosome &, Haplosome &, Haplosome *, std::vector *); +template void Population::HaplosomeCloned(Chromosome &, Haplosome &, Haplosome *, std::vector *); +template void Population::HaplosomeCloned(Chromosome &, Haplosome &, Haplosome *, std::vector *); // generate a child haplosome from parental haplosomes, with recombination and mutation, and user-specified breakpoints template void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector &p_breakpoints, std::vector *p_mutation_callbacks) { #if DEBUG + // Check template flags + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Population::HaplosomeRecombined): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + + // f_callbacks is sometimes over-broad, for complicated reasons, so we don't check it here + // This method is designed to run in parallel, but only if no callbacks are enabled if (p_mutation_callbacks) - THREAD_SAFETY_IN_ANY_PARALLEL("Population::HaplosomeRecombined(): recombination and mutation callbacks are not allowed when executing in parallel"); + THREAD_SAFETY_IN_ANY_PARALLEL("Population::HaplosomeRecombined(): mutation callbacks are not allowed when executing in parallel"); if (p_breakpoints.size() == 0) EIDOS_TERMINATION << "ERROR (Population::HaplosomeRecombined): (internal error) Called with an empty breakpoint array." << EidosTerminate(); @@ -4700,10 +4718,10 @@ void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_chil #endif } -template void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector &p_breakpoints, std::vector *p_mutation_callbacks); -template void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector &p_breakpoints, std::vector *p_mutation_callbacks); -template void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector &p_breakpoints, std::vector *p_mutation_callbacks); -template void Population::HaplosomeRecombined(Chromosome &p_chromosome, Haplosome &p_child_haplosome, Haplosome *parent_haplosome_1, Haplosome *parent_haplosome_2, std::vector &p_breakpoints, std::vector *p_mutation_callbacks); +template void Population::HaplosomeRecombined(Chromosome &, Haplosome &, Haplosome *, Haplosome *, std::vector &, std::vector *); +template void Population::HaplosomeRecombined(Chromosome &, Haplosome &, Haplosome *, Haplosome *, std::vector &, std::vector *); +template void Population::HaplosomeRecombined(Chromosome &, Haplosome &, Haplosome *, Haplosome *, std::vector &, std::vector *); +template void Population::HaplosomeRecombined(Chromosome &, Haplosome &, Haplosome *, Haplosome *, std::vector &, std::vector *); void Population::DoHeteroduplexRepair(std::vector &p_heteroduplex, slim_position_t *p_breakpoints, int p_breakpoints_count, Haplosome *p_parent_haplosome_1, Haplosome *p_parent_haplosome_2, Haplosome *p_child_haplosome) { diff --git a/core/species.cpp b/core/species.cpp index e2abc3d4..5bcf65a4 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1102,8 +1102,11 @@ template &pure_independent_dominance_traits) { #if DEBUG + // Check template flags + if (f_nonneutral_cache_regime != current_trait_calculation_regime_) + EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) f_nonneutral_cache_regime is incorrect." << EidosTerminate(); if (f_independent_dominance_present != (pure_independent_dominance_traits.size() > 0)) - EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) f_independent_dominance_present incorrect." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) f_independent_dominance_present is incorrect." << EidosTerminate(); #endif // This applies the specified nonneutral cache regime to all mutation runs in p_mutrun_pool, producing @@ -4100,7 +4103,7 @@ void Species::nonWF_GenerateOffspring(void) // note this optimization depends upon the fact that none of these flags can change during one reproduction() stage! bool pedigrees_enabled = PedigreesEnabled(); bool recording_tree_sequence = RecordingTreeSequence(); - bool has_reproduction_callbacks = ((reproduction_callbacks.size() > 0) || (modify_child_callbacks.size() > 0) || (recombination_callbacks.size() > 0) || (mutation_callbacks.size() > 0)); + bool has_callbacks = ((reproduction_callbacks.size() > 0) || (modify_child_callbacks.size() > 0) || (recombination_callbacks.size() > 0) || (mutation_callbacks.size() > 0)); bool is_spatial = (SpatialDimensionality() >= 1); if (DoingAnyMutationRunExperiments()) @@ -4109,7 +4112,7 @@ void Species::nonWF_GenerateOffspring(void) { if (recording_tree_sequence) { - if (has_reproduction_callbacks) // has any of the callbacks that the GenerateIndividuals...() methods care about; this can be refined later + if (has_callbacks) // has any of the callbacks that the GenerateIndividuals...() methods care about; this can be refined later { if (is_spatial) { @@ -4142,7 +4145,7 @@ void Species::nonWF_GenerateOffspring(void) } else { - if (has_reproduction_callbacks) + if (has_callbacks) { if (is_spatial) { @@ -4178,7 +4181,7 @@ void Species::nonWF_GenerateOffspring(void) { if (recording_tree_sequence) { - if (has_reproduction_callbacks) + if (has_callbacks) { if (is_spatial) { @@ -4211,7 +4214,7 @@ void Species::nonWF_GenerateOffspring(void) } else { - if (has_reproduction_callbacks) + if (has_callbacks) { if (is_spatial) { @@ -4250,7 +4253,7 @@ void Species::nonWF_GenerateOffspring(void) { if (recording_tree_sequence) { - if (has_reproduction_callbacks) + if (has_callbacks) { if (is_spatial) { @@ -4283,7 +4286,7 @@ void Species::nonWF_GenerateOffspring(void) } else { - if (has_reproduction_callbacks) + if (has_callbacks) { if (is_spatial) { @@ -4319,7 +4322,7 @@ void Species::nonWF_GenerateOffspring(void) { if (recording_tree_sequence) { - if (has_reproduction_callbacks) + if (has_callbacks) { if (is_spatial) { @@ -4352,7 +4355,7 @@ void Species::nonWF_GenerateOffspring(void) } else { - if (has_reproduction_callbacks) + if (has_callbacks) { if (is_spatial) { @@ -4389,7 +4392,7 @@ void Species::nonWF_GenerateOffspring(void) // similarly, choose templated variants for the HaplosomeCrossed()/HaplosomeCloned()/HaplosomeRecombined() methods of Population if (recording_tree_sequence) { - if (has_reproduction_callbacks) // has any of the callbacks that the GenerateIndividuals...() methods care about; this can be refined later + if (has_callbacks) // has any of the callbacks that the GenerateIndividuals...() methods care about; this can be refined later { population_.HaplosomeCrossed_TEMPLATED = &Population::HaplosomeCrossed; population_.HaplosomeCloned_TEMPLATED = &Population::HaplosomeCloned; @@ -4404,7 +4407,7 @@ void Species::nonWF_GenerateOffspring(void) } else { - if (has_reproduction_callbacks) + if (has_callbacks) { population_.HaplosomeCrossed_TEMPLATED = &Population::HaplosomeCrossed; population_.HaplosomeCloned_TEMPLATED = &Population::HaplosomeCloned; diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 26850ddc..b5a47236 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -2248,19 +2248,29 @@ Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Indi Subpopulation &parent2_subpop = *p_parent2->subpopulation_; #if DEBUG + // Check template flags + if (f_mutrunexps != species_.DoingAnyMutationRunExperiments()) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): (internal error) f_mutrunexps flag is incorrect." << EidosTerminate(); + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + IndividualSex parent1_sex = p_parent1->sex_; IndividualSex parent2_sex = p_parent2->sex_; if ((sex_enabled_ && (parent1_sex != IndividualSex::kFemale)) || (!sex_enabled_ && (parent1_sex != IndividualSex::kHermaphrodite))) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualCrossed): parent1 must be female in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): parent1 must be female in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); if ((sex_enabled_ && (parent2_sex != IndividualSex::kMale)) || (!sex_enabled_ && (parent2_sex != IndividualSex::kHermaphrodite))) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualCrossed): parent2 must be male in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): parent2 must be male in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); if ((p_parent1->index_ == -1) || (p_parent2->index_ == -1)) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualCrossed): parent1 and parent2 must be visible in a subpopulation (i.e., may not be new juveniles)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): parent1 and parent2 must be visible in a subpopulation (i.e., may not be new juveniles)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if ((&parent1_subpop.species_ != &this->species_) || (&parent2_subpop.species_ != &this->species_)) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualCrossed): addCrossed() requires that both parents belong to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): addCrossed() requires that both parents belong to the same species as the target subpopulation." << EidosTerminate(); #endif // Figure out callbacks, which are based on the subpopulation of each parent @@ -2308,7 +2318,7 @@ Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Indi { #if DEBUG if (!species_.HasGenetics()) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualCrossed): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); #endif if (f_mutrunexps) chromosome->StartMutationRunExperimentClock(); @@ -2506,7 +2516,7 @@ Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Indi } case ChromosomeType::kHNull_HaploidAutosomeWithNull: { - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualCrossed): chromosome type 'H-' does not allow reproduction by biparental cross (only cloning); chromosome type 'H' provides greater flexibility for modeling haploids." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCrossed): chromosome type 'H-' does not allow reproduction by biparental cross (only cloning); chromosome type 'H' provides greater flexibility for modeling haploids." << EidosTerminate(); break; } case ChromosomeType::kNullY_YSexChromosomeWithNull: @@ -2592,38 +2602,38 @@ Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Indi return individual; } -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); +template Individual *Subpopulation::GenerateIndividualCrossed(Individual *, Individual *, IndividualSex); template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent) @@ -2631,16 +2641,26 @@ Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent) Subpopulation &parent_subpop = *p_parent->subpopulation_; #if DEBUG + // Check template flags + if (f_mutrunexps != species_.DoingAnyMutationRunExperiments()) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualSelfed): (internal error) f_mutrunexps flag is incorrect." << EidosTerminate(); + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualSelfed): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualSelfed): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualSelfed): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + IndividualSex parent_sex = p_parent->sex_; if (parent_sex != IndividualSex::kHermaphrodite) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualSelfed): parent must be hermaphroditic." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualSelfed): parent must be hermaphroditic." << EidosTerminate(); if (p_parent->index_ == -1) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualSelfed): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualSelfed): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if (&parent_subpop.species_ != &this->species_) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualSelfed): addSelfed() requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualSelfed): addSelfed() requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); #endif // Figure out callbacks, which are based on the subpopulation of each parent @@ -2682,7 +2702,7 @@ Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent) { #if DEBUG if (!species_.HasGenetics()) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualSelfed): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualSelfed): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); #endif if (f_mutrunexps) chromosome->StartMutationRunExperimentClock(); @@ -2721,7 +2741,7 @@ Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent) } case ChromosomeType::kHNull_HaploidAutosomeWithNull: { - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualSelfed): chromosome type 'H-' does not allow reproduction by selfing (only cloning); chromosome type 'H' provides greater flexibility for modeling haploids." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualSelfed): chromosome type 'H-' does not allow reproduction by selfing (only cloning); chromosome type 'H' provides greater flexibility for modeling haploids." << EidosTerminate(); break; } case ChromosomeType::kX_XSexChromosome: @@ -2731,7 +2751,7 @@ Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent) case ChromosomeType::kFL_HaploidFemaleLine: case ChromosomeType::kML_HaploidMaleLine: case ChromosomeType::kNullY_YSexChromosomeWithNull: - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualEmpty): (internal error) sex-specific chromosome type not supported for selfing." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualEmpty): (internal error) sex-specific chromosome type not supported for selfing." << EidosTerminate(); break; } @@ -2789,38 +2809,38 @@ Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent) return individual; } -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); +template Individual *Subpopulation::GenerateIndividualSelfed(Individual *); template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent) @@ -2829,12 +2849,22 @@ Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent) Subpopulation &parent_subpop = *p_parent->subpopulation_; #if DEBUG + // Check template flags + if (f_mutrunexps != species_.DoingAnyMutationRunExperiments()) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCloned): (internal error) f_mutrunexps flag is incorrect." << EidosTerminate(); + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCloned): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCloned): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCloned): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + if (p_parent->index_ == -1) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualCloned): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCloned): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if (&parent_subpop.species_ != &this->species_) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualCloned): addCloned() requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCloned): addCloned() requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); #endif // Figure out callbacks, which are based on the subpopulation of each parent @@ -2873,7 +2903,7 @@ Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent) { #if DEBUG if (!species_.HasGenetics()) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualCloned): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualCloned): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); #endif if (f_mutrunexps) chromosome->StartMutationRunExperimentClock(); @@ -2978,38 +3008,38 @@ Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent) return individual; } -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); -template Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); +template Individual *Subpopulation::GenerateIndividualCloned(Individual *); Individual *Subpopulation::GenerateIndividualEmpty(slim_popsize_t p_individual_index, IndividualSex p_child_sex, slim_age_t p_age, slim_fitness_t p_fitness, float p_mean_parent_age, bool p_haplosome1_null, bool p_haplosome2_null, bool p_run_modify_child, bool p_record_in_treeseq) { @@ -3036,7 +3066,7 @@ Individual *Subpopulation::GenerateIndividualEmpty(slim_popsize_t p_individual_i { #if DEBUG if (!species_.HasGenetics()) - EIDOS_TERMINATION << "ERROR (Population::GenerateIndividualEmpty): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::GenerateIndividualEmpty): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); #endif chromosome->StartMutationRunExperimentClock(); @@ -3254,24 +3284,36 @@ bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigree Subpopulation &parent1_subpop = *p_parent1->subpopulation_; #if DEBUG + // Check template flags + bool mutrun_exp_timing_per_individual = species_.DoingAnyMutationRunExperiments() && (species_.Chromosomes().size() > 1); + + if (f_mutrunexps != mutrun_exp_timing_per_individual) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): (internal error) f_mutrunexps flag is incorrect." << EidosTerminate(); + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + Subpopulation &parent2_subpop = *p_parent2->subpopulation_; if (&parent1_subpop != &parent2_subpop) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed): parent1 and parent2 must belong to the same subpopulation; that is assumed, since this method is called only for WF reproduction." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): parent1 and parent2 must belong to the same subpopulation; that is assumed, since this method is called only for WF reproduction." << EidosTerminate(); IndividualSex parent1_sex = p_parent1->sex_; IndividualSex parent2_sex = p_parent2->sex_; if ((sex_enabled_ && (parent1_sex != IndividualSex::kFemale)) || (!sex_enabled_ && (parent1_sex != IndividualSex::kHermaphrodite))) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed): parent1 must be female in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): parent1 must be female in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); if ((sex_enabled_ && (parent2_sex != IndividualSex::kMale)) || (!sex_enabled_ && (parent2_sex != IndividualSex::kHermaphrodite))) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed): parent2 must be male in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): parent2 must be male in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); if ((p_parent1->index_ == -1) || (p_parent2->index_ == -1)) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed): parent1 and parent2 must be visible in a subpopulation (i.e., may not be new juveniles)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): parent1 and parent2 must be visible in a subpopulation (i.e., may not be new juveniles)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if ((&parent1_subpop.species_ != &this->species_) || (&parent2_subpop.species_ != &this->species_)) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed): biparental crossing requires that both parents belong to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): biparental crossing requires that both parents belong to the same species as the target subpopulation." << EidosTerminate(); #endif // Figure out callbacks, which are based on the subpopulation of the parents (which must be the same) @@ -3314,7 +3356,7 @@ bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigree { #if DEBUG if (!species_.HasGenetics()) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); #endif if (f_mutrunexps) chromosome->StartMutationRunExperimentClock(); @@ -3529,7 +3571,7 @@ bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigree } case ChromosomeType::kHNull_HaploidAutosomeWithNull: { - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed): chromosome type 'H-' does not allow reproduction by biparental cross (only cloning); chromosome type 'H' provides greater flexibility for modeling haploids." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed): chromosome type 'H-' does not allow reproduction by biparental cross (only cloning); chromosome type 'H' provides greater flexibility for modeling haploids." << EidosTerminate(); break; } case ChromosomeType::kNullY_YSexChromosomeWithNull: @@ -3620,63 +3662,71 @@ bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigree return true; } -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); // this more limited templated variant assumes there is one chromosome, the chromosome type is "A", and f_mutrunexps=F and f_callbacks=F template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, __attribute__ ((unused)) IndividualSex p_child_sex) { #if DEBUG + // Check template flags + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_A): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_A): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_A): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + Subpopulation &parent1_subpop = *p_parent1->subpopulation_; Subpopulation &parent2_subpop = *p_parent2->subpopulation_; if (&parent1_subpop != &parent2_subpop) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): parent1 and parent2 must belong to the same subpopulation; that is assumed, since this method is called only for WF reproduction." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_A): parent1 and parent2 must belong to the same subpopulation; that is assumed, since this method is called only for WF reproduction." << EidosTerminate(); IndividualSex parent1_sex = p_parent1->sex_; IndividualSex parent2_sex = p_parent2->sex_; if ((sex_enabled_ && (parent1_sex != IndividualSex::kFemale)) || (!sex_enabled_ && (parent1_sex != IndividualSex::kHermaphrodite))) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): parent1 must be female in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_A): parent1 must be female in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); if ((sex_enabled_ && (parent2_sex != IndividualSex::kMale)) || (!sex_enabled_ && (parent2_sex != IndividualSex::kHermaphrodite))) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): parent2 must be male in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_A): parent2 must be male in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); if ((p_parent1->index_ == -1) || (p_parent2->index_ == -1)) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): parent1 and parent2 must be visible in a subpopulation (i.e., may not be new juveniles)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_A): parent1 and parent2 must be visible in a subpopulation (i.e., may not be new juveniles)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if ((&parent1_subpop.species_ != &this->species_) || (&parent2_subpop.species_ != &this->species_)) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): biparental crossing requires that both parents belong to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_A): biparental crossing requires that both parents belong to the same species as the target subpopulation." << EidosTerminate(); #endif // Record the offspring @@ -3731,39 +3781,47 @@ bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pe return true; } -template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); +template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_A(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); // this more limited templated variant assumes there is one chromosome, the chromosome type is "H", and f_mutrunexps=F and f_callbacks=F template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, __attribute__ ((unused)) IndividualSex p_child_sex) { #if DEBUG + // Check template flags + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_H): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_H): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_H): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + Subpopulation &parent1_subpop = *p_parent1->subpopulation_; Subpopulation &parent2_subpop = *p_parent2->subpopulation_; if (&parent1_subpop != &parent2_subpop) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): parent1 and parent2 must belong to the same subpopulation; that is assumed, since this method is called only for WF reproduction." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_H): parent1 and parent2 must belong to the same subpopulation; that is assumed, since this method is called only for WF reproduction." << EidosTerminate(); IndividualSex parent1_sex = p_parent1->sex_; IndividualSex parent2_sex = p_parent2->sex_; if ((sex_enabled_ && (parent1_sex != IndividualSex::kFemale)) || (!sex_enabled_ && (parent1_sex != IndividualSex::kHermaphrodite))) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): parent1 must be female in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_H): parent1 must be female in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); if ((sex_enabled_ && (parent2_sex != IndividualSex::kMale)) || (!sex_enabled_ && (parent2_sex != IndividualSex::kHermaphrodite))) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): parent2 must be male in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_H): parent2 must be male in sexual models, or hermaphroditic in non-sexual models." << EidosTerminate(); if ((p_parent1->index_ == -1) || (p_parent2->index_ == -1)) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): parent1 and parent2 must be visible in a subpopulation (i.e., may not be new juveniles)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_H): parent1 and parent2 must be visible in a subpopulation (i.e., may not be new juveniles)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if ((&parent1_subpop.species_ != &this->species_) || (&parent2_subpop.species_ != &this->species_)) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCrossed_1CH): biparental crossing requires that both parents belong to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCrossed_1CH_H): biparental crossing requires that both parents belong to the same species as the target subpopulation." << EidosTerminate(); #endif // Record the offspring @@ -3807,14 +3865,14 @@ bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pe return true; } -template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); -template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex); +template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); +template bool Subpopulation::MungeIndividualCrossed_1CH_H(Individual *, slim_pedigreeid_t, Individual *, Individual *, IndividualSex); template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent) @@ -3822,16 +3880,28 @@ bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreei Subpopulation &parent_subpop = *p_parent->subpopulation_; #if DEBUG + // Check template flags + bool mutrun_exp_timing_per_individual = species_.DoingAnyMutationRunExperiments() && (species_.Chromosomes().size() > 1); + + if (f_mutrunexps != mutrun_exp_timing_per_individual) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): (internal error) f_mutrunexps flag is incorrect." << EidosTerminate(); + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + IndividualSex parent_sex = p_parent->sex_; if (parent_sex != IndividualSex::kHermaphrodite) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualSelfed): parent must be hermaphroditic." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): parent must be hermaphroditic." << EidosTerminate(); if (p_parent->index_ == -1) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualSelfed): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if (&parent_subpop.species_ != &this->species_) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualSelfed): selfing requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): selfing requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); #endif // Figure out callbacks, which are based on the subpopulation of each parent @@ -3874,7 +3944,7 @@ bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreei { #if DEBUG if (!species_.HasGenetics()) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualSelfed): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); #endif if (f_mutrunexps) chromosome->StartMutationRunExperimentClock(); @@ -3917,7 +3987,7 @@ bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreei } case ChromosomeType::kHNull_HaploidAutosomeWithNull: { - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualSelfed): chromosome type 'H-' does not allow reproduction by selfing (only cloning); chromosome type 'H' provides greater flexibility for modeling haploids." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): chromosome type 'H-' does not allow reproduction by selfing (only cloning); chromosome type 'H' provides greater flexibility for modeling haploids." << EidosTerminate(); break; } case ChromosomeType::kX_XSexChromosome: @@ -3927,7 +3997,7 @@ bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreei case ChromosomeType::kFL_HaploidFemaleLine: case ChromosomeType::kML_HaploidMaleLine: case ChromosomeType::kNullY_YSexChromosomeWithNull: - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualSelfed): (internal error) sex-specific chromosome type not supported for selfing." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualSelfed): (internal error) sex-specific chromosome type not supported for selfing." << EidosTerminate(); break; } @@ -3989,38 +4059,38 @@ bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreei return true; } -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualSelfed(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualSelfed(Individual *, slim_pedigreeid_t, Individual *); template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent) @@ -4029,14 +4099,26 @@ bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreei Subpopulation &parent_subpop = *p_parent->subpopulation_; #if DEBUG + // Check template flags + bool mutrun_exp_timing_per_individual = species_.DoingAnyMutationRunExperiments() && (species_.Chromosomes().size() > 1); + + if (f_mutrunexps != mutrun_exp_timing_per_individual) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): (internal error) f_mutrunexps flag is incorrect." << EidosTerminate(); + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + if (p_parent->index_ == -1) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); if (individual->sex_ != parent_sex) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): child sex does not match parent sex (which, for cloning, it should)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): child sex does not match parent sex (which, for cloning, it should)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if (&parent_subpop.species_ != &this->species_) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): cloning requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): cloning requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); #endif // Figure out callbacks, which are based on the subpopulation of the parents (which must be the same) @@ -4077,7 +4159,7 @@ bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreei { #if DEBUG if (!species_.HasGenetics()) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); #endif if (f_mutrunexps) chromosome->StartMutationRunExperimentClock(); @@ -4280,54 +4362,62 @@ bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreei return true; } -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned(Individual *, slim_pedigreeid_t, Individual *); template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent) { #if DEBUG + // Check template flags + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned_1CH_A): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned_1CH_A): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned_1CH_A): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + IndividualSex parent_sex = p_parent->sex_; Subpopulation &parent_subpop = *p_parent->subpopulation_; if (p_parent->index_ == -1) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); if (individual->sex_ != parent_sex) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): child sex does not match parent sex (which, for cloning, it should)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): child sex does not match parent sex (which, for cloning, it should)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if (&parent_subpop.species_ != &this->species_) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): cloning requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): cloning requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); #endif // Record the offspring @@ -4353,7 +4443,7 @@ bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_ped #if DEBUG if (!species_.HasGenetics()) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); #endif // Determine what kind of haplosomes to make for this chromosome @@ -4391,30 +4481,38 @@ bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_ped return true; } -template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); +template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_A(Individual *, slim_pedigreeid_t, Individual *); template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent) { #if DEBUG + // Check template flags + if (f_pedigree_rec != species_.PedigreesEnabled()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned_1CH_H): (internal error) f_pedigree_rec flag is incorrect." << EidosTerminate(); + if (f_treeseq != species_.RecordingTreeSequence()) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned_1CH_H): (internal error) f_treeseq flag is incorrect." << EidosTerminate(); + if (f_spatial != (species_.SpatialDimensionality() >= 1)) + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned_1CH_H): (internal error) f_spatial flag is incorrect." << EidosTerminate(); + IndividualSex parent_sex = p_parent->sex_; Subpopulation &parent_subpop = *p_parent->subpopulation_; if (p_parent->index_ == -1) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): parent must be visible in a subpopulation (i.e., may not be a new juvenile)." << EidosTerminate(); if (individual->sex_ != parent_sex) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): child sex does not match parent sex (which, for cloning, it should)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): child sex does not match parent sex (which, for cloning, it should)." << EidosTerminate(); // SPECIES CONSISTENCY CHECK if (&parent_subpop.species_ != &this->species_) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): cloning requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): cloning requires that parent belongs to the same species as the target subpopulation." << EidosTerminate(); #endif // Record the offspring @@ -4440,7 +4538,7 @@ bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_ped #if DEBUG if (!species_.HasGenetics()) - EIDOS_TERMINATION << "ERROR (Population::MungeIndividualCloned): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Subpopulation::MungeIndividualCloned): (internal error) a chromosome is defined for a no-genetics species!" << EidosTerminate(); #endif Haplosome *haplosome1; @@ -4463,14 +4561,14 @@ bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_ped return true; } -template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); -template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent); +template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *, slim_pedigreeid_t, Individual *); +template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *, slim_pedigreeid_t, Individual *); // nonWF only: void Subpopulation::ApplyReproductionCallbacks(std::vector &p_reproduction_callbacks, slim_popsize_t p_individual_index) From a6f57f36cc09470171fa3c7b16d303b17c913569 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 21 Jan 2026 15:46:10 -0600 Subject: [PATCH 085/107] add Offset property, polish dynamic properties --- QtSLiM/QtSLiM_Plot.cpp | 2 +- QtSLiM/QtSLiM_Plot.h | 2 +- QtSLiM/QtSLiM_SLiMgui.cpp | 2 +- QtSLiM/QtSLiM_SLiMgui.h | 2 +- QtSLiM/help/SLiMHelpClasses.html | 10 +- SLiMgui/SLiMHelpClasses.rtf | 41 ++++- SLiMgui/plot.h | 2 +- SLiMgui/plot.mm | 4 +- SLiMgui/slim_gui.h | 2 +- SLiMgui/slim_gui.mm | 4 +- VERSIONS | 1 + core/chromosome.cpp | 4 +- core/chromosome.h | 2 +- core/community.h | 2 +- core/community_eidos.cpp | 4 +- core/genomic_element.cpp | 4 +- core/genomic_element.h | 2 +- core/genomic_element_type.cpp | 4 +- core/genomic_element_type.h | 2 +- core/haplosome.cpp | 4 +- core/haplosome.h | 2 +- core/individual.cpp | 284 ++++++++++++++++++++++++++++-- core/individual.h | 4 +- core/interaction_type.cpp | 4 +- core/interaction_type.h | 2 +- core/log_file.cpp | 4 +- core/log_file.h | 2 +- core/mutation.cpp | 12 +- core/mutation.h | 2 +- core/mutation_type.cpp | 8 +- core/mutation_type.h | 2 +- core/slim_eidos_block.cpp | 7 +- core/slim_eidos_block.h | 2 +- core/slim_test_genetics.cpp | 58 +++++- core/spatial_map.cpp | 4 +- core/spatial_map.h | 2 +- core/species.h | 2 +- core/species_eidos.cpp | 32 +++- core/subpopulation.cpp | 4 +- core/subpopulation.h | 2 +- core/substitution.cpp | 4 +- core/substitution.h | 2 +- core/trait.cpp | 4 +- core/trait.h | 2 +- eidos/eidos_class_DataFrame.cpp | 4 +- eidos/eidos_class_DataFrame.h | 2 +- eidos/eidos_class_Dictionary.cpp | 4 +- eidos/eidos_class_Dictionary.h | 2 +- eidos/eidos_class_Image.cpp | 4 +- eidos/eidos_class_Image.h | 2 +- eidos/eidos_class_Object.cpp | 34 +++- eidos/eidos_class_Object.h | 7 +- eidos/eidos_class_TestElement.cpp | 8 +- eidos/eidos_class_TestElement.h | 4 +- 54 files changed, 513 insertions(+), 109 deletions(-) diff --git a/QtSLiM/QtSLiM_Plot.cpp b/QtSLiM/QtSLiM_Plot.cpp index 0caa895e..39129c28 100644 --- a/QtSLiM/QtSLiM_Plot.cpp +++ b/QtSLiM/QtSLiM_Plot.cpp @@ -1866,7 +1866,7 @@ const std::vector *Plot_Class::Properties(void) cons if (!properties) { - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_title, true, kEidosValueMaskString | kEidosValueMaskSingleton))); diff --git a/QtSLiM/QtSLiM_Plot.h b/QtSLiM/QtSLiM_Plot.h index 88a5305f..b776b0ed 100644 --- a/QtSLiM/QtSLiM_Plot.h +++ b/QtSLiM/QtSLiM_Plot.h @@ -92,7 +92,7 @@ class Plot_Class : public EidosDictionaryUnretained_Class Plot_Class& operator=(const Plot_Class&) = delete; // no copying inline Plot_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/QtSLiM/QtSLiM_SLiMgui.cpp b/QtSLiM/QtSLiM_SLiMgui.cpp index 4c0d1c98..74207ac8 100644 --- a/QtSLiM/QtSLiM_SLiMgui.cpp +++ b/QtSLiM/QtSLiM_SLiMgui.cpp @@ -305,7 +305,7 @@ const std::vector *SLiMgui_Class::Properties(void) c if (!properties) { - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back(static_cast((new EidosPropertySignature(gStr_pid, true, kEidosValueMaskInt | kEidosValueMaskSingleton)))); diff --git a/QtSLiM/QtSLiM_SLiMgui.h b/QtSLiM/QtSLiM_SLiMgui.h index 23cff744..8d18bf83 100644 --- a/QtSLiM/QtSLiM_SLiMgui.h +++ b/QtSLiM/QtSLiM_SLiMgui.h @@ -86,7 +86,7 @@ class SLiMgui_Class : public EidosDictionaryUnretained_Class SLiMgui_Class& operator=(const SLiMgui_Class&) = delete; // no copying inline SLiMgui_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 4361db43..ed596ef2 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -380,6 +380,8 @@

5.7.1  Individual properties

<trait-name> <–> (float$)

This dynamic property provides the phenotype of the individual for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The phenotypeForTrait() method provides an alternative way to access phenotype values.

+

<trait-name>Offset <–> (float$)

+

This dynamic property provides the individual offset for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The offsetForTrait() method provides an alternative way to access individual offsets.

age <–> (integer$)

The age of the individual, measured in cycles.  A newly generated offspring individual will have an age of 0 in the same tick in which it was created.  The age of every individual is incremented by one at the same point that its species cycle counter is incremented, at the end of the tick cycle, if and only if its species was active in that tick.  The age of individuals may be changed; usually this only makes sense when setting up the initial state of a model, however.

cachedFitness => (float$)

@@ -478,7 +480,7 @@

The returned vector will contain tranches of mutations, one tranche per chromosome, in the order that the chromosomes were specified (if chromosomes is non-NULL) or the order the chromosomes were defined in the model (if chromosomes is NULL).  Within a given tranche, the mutations for that chromosome will be returned in sorted order by position.  (If more than one mutation associated with a given chromosome exists at the same position, the order in which those mutations are returned is undefined.)

This method replaces the deprecated method uniqueMutationsOfType(), while providing additional useful options.  It is particularly useful for efficient, vectorized assessment of the homozygous versus heterozygous state of the mutations contained by an individual, which is otherwise difficult to assess efficiently.  See also the method zygosityOfMutations(), which provides an alternative approach for assessing the zygosity of mutations in an individual.

– (float)offsetForTrait([Niso<Trait> trait = NULL])

-

Returns the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+

Returns the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by trait.  Note that the offset for a single trait can also be accessed directly through the <trait-name>Offset property of Individual.  Also see setOffsetForTrait() for changing individual offset values.

+ (void)outputIndividuals([Ns$ filePath = NULL], [logical$ append = F], [Niso<Chromosome>$ chromosome = NULL], [logical$ spatialPositions = T], [logical$ ages = T], [logical$ ancestralNucleotides = F], [logical$ pedigreeIDs = F], [logical$ objectTags = F])

Output the state of the target vector of individuals in SLiM's own format.  If the optional parameter filePath is NULL (the default), output will be sent to Eidos’s output stream.  Otherwise, output will be sent to the filesystem path specified by filePath, overwriting that file if append if F, or appending to the end of it if append is T.  This method is quite similar to the Species method outputFull(), but (1) it can produce output for any vector of individuals, not always for the entire population; (2) it does not support output in a binary format; (3) it can produce output regarding the genetics for all chromosomes or for just one focal chromosome; and (4) there is no corresponding read method, as readFromPopulationFile() can read the data saved by outputFull().

The chromosome parameter specifies a focal chromosome for which the genetics of the target individuals will be output.  If chromosome is NULL, all chromosomes will be output; otherwise, chromosome may specify the focal chromosome with an integer chromosome id, a string chromosome symbol, or a Chromosome object.

@@ -494,7 +496,7 @@

The parameters outputMultiallelics, simplifyNucleotides, and outputNonnucleotides affect the format of the output produced.

Output is generally done in a late() event, so that the output reflects the state of the simulation at the end of a tick.

– (float)phenotypeForTrait([Niso<Trait> trait = NULL])

-

Returns the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by trait.

+

Returns the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by trait.  Note that the phenotype for a single trait can also be accessed directly through the <trait-name> property of Individual.  Also see setPhenotypeForTrait() for changing individual phenotypes.

+ (object<Mutation>)readIndividualsFromVCF(string$ filePath, [Nio<MutationType>$ mutationType = NULL])

Read new mutations from the VCF format file at filePath and add them to the target individuals.  The number of target individuals must match the number of samples represented in the VCF file; each sample will be associated with a corresponding target individual, in the order that the samples and the target individuals are provided.  To read into all of the individuals in a given subpopulation pN, simply call pN.individuals.readIndividualsFromVCF(), assuming the subpopulation’s size matches the number of samples in the VCF file.  A vector containing all of the mutations created by readIndividualsFromVCF() is returned (not necessarily in the order of the corresponding VCF call lines).

This method and the readHaplosomesFromVCF() method of Haplosome provide two alternative ways of reading VCF data, focused on the perspective of either individuals (this method) or haplosomes (the Haplosome method).  As described above, this method draws a correspondence between VCF samples and individuals, whereas the Haplosome method draws a correspondence between VCF calls and haplosomes.  For example, if a VCF call line contained a series of calls like “1|1 0|1 1 0 1|0” this method would see that as calls for five individuals, three of which are diploid for the chromosome being called, and two of which are haploid.  That would make sense if, for example, the chromosome being called is an X chromosome; the diploid individuals would be females, the haploid individuals would be males.  The vector of target individuals would need to contain two females, then two males, and then a female, so that the structure of the calls matched the haplosome structure of the individuals, or an error would result.  The readHaplosomesFromVCF() method of Haplosome, on the other hand, would see that same series of calls as corresponding to eight haplosomes, and would assign each call to the corresponding non-null target haplosome, without regard to whether the VCF’s grouping into diploid and haploid calls corresponded to any coherent structure of individuals in the SLiM model.  Each approach has advantages and disadvantages.  This method provides much more error-checking and safety when your intention is to read individual-level data from VCF; it can check that the ploidy in the VCF data matches the ploidy of each target individual, and that diploid calls like 1|1 get assigned to the two haplosomes of a single individual correctly.  The readHaplosomesFromVCF() method of Haplosome does not perform such checks, and can push VCF data into any arbitrary set of haplosomes, so it provides more power and flexibility, but less error-checking and less intelligence.

@@ -509,11 +511,11 @@

This method assumes that the grandparents (or the parents, if grandparental information is not available) are themselves unrelated and that they are not inbred; this assumption is necessary because we have no information about their parentage, since SLiM’s pedigree tracking information only goes back two generations.  Be aware that in a model where inbreeding or selfing occurs at all (including “incidental selfing”, where a hermaphroditic individual happens to choose itself as a mate), some level of “background relatedness” will be present and this assumption will be violated.  In such circumstances, relatedness() will therefore tend to underestimate the degree of relatedness between individuals, and the greater the degree of inbreeding, the greater the underestimation will be.  If inbreeding is allowed in a model – and particularly if it is common – the results of relatedness() should therefore not be taken as an estimate of absolute relatedness, but can still be useful as an estimate of relative relatedness (indicating that, say, A appears from the information available to be more closely related to B than it is to C).

See also sharedParentCount() for a different metric of relatedness.

+ (void)setOffsetForTrait([Niso<Trait> trait = NULL], [Nif offset = NULL])

-

Sets the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

Sets the individual offset(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Note that the offset for a single trait can also be accessed directly through the <trait-name>Offset property of Individual.

The parameter offset must follow one of four patterns.  In the first pattern, offset is NULL; this draws the offset for each of the specified traits from each trait’s individual-offset distribution (defined by each trait’s individualOffsetMean and individualOffsetSD properties) in each target individual.  (Note that individual offsets are automatically drawn from these distributions when an individual is created; this re-draws new offset values.)  In the second pattern, offset is a singleton value; this sets the given offset for each of the specified traits in each target individual.  In the third pattern, offset is of length equal to the number of specified traits; this sets the offset for each of the specified traits to the corresponding offset value in each target individual.  In the fourth pattern, offset is of length equal to the number of specified traits times the number of target individuals; this uses offset to provide a different offset value for each trait in each individual, using consecutive values from offset to set the offset for each of the specified traits in one individual before moving to the next individual.

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  This policy applies to individual offsets, so an offset value passed in or generated here may not be the value actually used by SLiM or subsequently returned by offsetForTrait().

+ (void)setPhenotypeForTrait(Niso<Trait> trait, numeric phenotype)

-

Sets the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

Sets the individual phenotype(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Note that the phenotype for a single trait can also be accessed directly through the <trait-name> property of Individual.

The parameter phenotype must follow one of three patterns.  In the first pattern, phenotype is a singleton value; this sets the given phenotype for each of the specified traits in each target individual.  In the second pattern, phenotype is of length equal to the number of specified traits; this sets the phenotype for each of the specified traits to the corresponding phenotype in each target individual.  In the third pattern, phenotype is of length equal to the number of specified traits times the number of target individuals; this uses phenotype to provide a different phenotype for each trait in each individual, using consecutive values from phenotype to set the phenotype for each of the specified traits in one individual before moving to the next individual.

+ (void)setSpatialPosition(float position)

Sets the spatial position of the individual (as accessed through the spatialPosition property).  The length of position (the number of coordinates in the spatial position of an individual) depends upon the spatial dimensionality declared with initializeSLiMOptions().  If the spatial dimensionality is zero (as it is by default), it is an error to call this method.  The elements of position are set into the values of the x, y, and z properties (if those properties are encompassed by the spatial dimensionality of the simulation).  In other words, if the declared dimensionality is "xy", calling individual.setSpatialPosition(c(1.0, 0.5)) property is equivalent to individual.x = 1.0; individual.y = 0.5; individual.z is not set (even if a third value is supplied in position) since it is not encompassed by the simulation’s dimensionality in this example.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 4fab3fa1..8a6f3910 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -2937,6 +2937,17 @@ The species to which the target object belongs.\ \f4\fs20 method provides an alternative way to access phenotype values.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f2\i\fs18 \cf2 +\f3\i0 Offset <\'96> (float$)\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 This dynamic property provides the individual offset for the trait named +\f2\i\fs18 +\f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The +\f3\fs18 offsetForTrait() +\f4\fs20 method provides an alternative way to access individual offsets.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf0 age \cf2 \expnd0\expndtw0\kerning0 <\'96>\cf0 \kerning1\expnd0\expndtw0 (integer$)\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 @@ -3908,7 +3919,14 @@ This method replaces the deprecated method \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Offsets for a given target individual will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait -\f4\fs20 .\ +\f4\fs20 . Note that the offset for a single trait can also be accessed directly through the +\f2\i\fs18 +\f3\i0 Offset +\f4\fs20 property of +\f3\fs18 Individual +\f4\fs20 . Also see +\f3\fs18 setOffsetForTrait() +\f4\fs20 for changing individual offset values.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 +\'a0(void)outputIndividuals([Ns$\'a0filePath\'a0=\'a0NULL], [logical$\'a0append\'a0=\'a0F], [Niso$\'a0chromosome\'a0=\'a0NULL], [logical$\'a0spatialPositions\'a0=\'a0T], [logical$\'a0ages\'a0=\'a0T], [logical$\'a0ancestralNucleotides\'a0=\'a0F], [logical$\'a0pedigreeIDs\'a0=\'a0F], [logical$\'a0objectTags\'a0=\'a0F])\ @@ -4120,7 +4138,13 @@ Output is generally done in a \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait -\f4\fs20 .\ +\f4\fs20 . Note that the phenotype for a single trait can also be accessed directly through the +\f2\i\fs18 +\f4\i0\fs20 property of +\f3\fs18 Individual +\f4\fs20 . Also see +\f3\fs18 setPhenotypeForTrait() +\f4\fs20 for changing individual phenotypes.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 +\'a0(object)readIndividualsFromVCF(string$\'a0filePath, [Nio$\'a0mutationType\'a0=\'a0NULL])\ @@ -4300,7 +4324,12 @@ See also \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL -\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Note that the offset for a single trait can also be accessed directly through the +\f2\i\fs18 +\f3\i0 Offset +\f4\fs20 property of +\f3\fs18 Individual +\f4\fs20 .\ The parameter \f3\fs18 offset \f4\fs20 must follow one of four patterns. In the first pattern, @@ -4344,7 +4373,11 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL -\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Note that the phenotype for a single trait can also be accessed directly through the +\f2\i\fs18 +\f4\i0\fs20 property of +\f3\fs18 Individual +\f4\fs20 .\ The parameter \f3\fs18 phenotype \f4\fs20 must follow one of three patterns. In the first pattern, diff --git a/SLiMgui/plot.h b/SLiMgui/plot.h index 5fb7aaa7..cd4b5cb6 100644 --- a/SLiMgui/plot.h +++ b/SLiMgui/plot.h @@ -95,7 +95,7 @@ class Plot_Class : public EidosDictionaryUnretained_Class Plot_Class& operator=(const Plot_Class&) = delete; // no copying inline Plot_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/SLiMgui/plot.mm b/SLiMgui/plot.mm index 26ebb1e5..4b2af63a 100644 --- a/SLiMgui/plot.mm +++ b/SLiMgui/plot.mm @@ -279,13 +279,13 @@ Plot_Class *gSLiM_Plot_Class = nullptr; -const std::vector *Plot_Class::Properties(void) const +std::vector *Plot_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; if (!properties) { - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_title, true, kEidosValueMaskString | kEidosValueMaskSingleton))); diff --git a/SLiMgui/slim_gui.h b/SLiMgui/slim_gui.h index e30ca09d..adb3bb2f 100644 --- a/SLiMgui/slim_gui.h +++ b/SLiMgui/slim_gui.h @@ -92,7 +92,7 @@ class SLiMgui_Class : public EidosDictionaryUnretained_Class SLiMgui_Class& operator=(const SLiMgui_Class&) = delete; // no copying inline SLiMgui_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/SLiMgui/slim_gui.mm b/SLiMgui/slim_gui.mm index d5beede5..8fcbafc1 100644 --- a/SLiMgui/slim_gui.mm +++ b/SLiMgui/slim_gui.mm @@ -198,13 +198,13 @@ SLiMgui_Class *gSLiM_SLiMgui_Class = nullptr; -const std::vector *SLiMgui_Class::Properties(void) const +std::vector *SLiMgui_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; if (!properties) { - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_pid, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); diff --git a/VERSIONS b/VERSIONS index ac875e57..78aeec04 100644 --- a/VERSIONS +++ b/VERSIONS @@ -147,6 +147,7 @@ multitrait branch: add a new return option for mutationEffect() callbacks: NULL now indicates neutrality for the focal trait, meaning either 1.0 (multiplicative) or 0.0 (additive) re-enable non-neutral caching (but without independent dominance optimizations, so far) add calculation and use of independent dominance caches, per trait + add a dynamic property to Individual for accessing individual offsets, Offset version 5.1 (Eidos version 4.1): diff --git a/core/chromosome.cpp b/core/chromosome.cpp index abc33fd4..29f0dcdf 100644 --- a/core/chromosome.cpp +++ b/core/chromosome.cpp @@ -3753,7 +3753,7 @@ EidosValue_SP Chromosome::ExecuteMethod_setRecombinationRate(EidosGlobalStringID Chromosome_Class *gSLiM_Chromosome_Class = nullptr; -const std::vector *Chromosome_Class::Properties(void) const +std::vector *Chromosome_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -3761,7 +3761,7 @@ const std::vector *Chromosome_Class::Properties(void { THREAD_SAFETY_IN_ANY_PARALLEL("Chromosome_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_genomicElements, true, kEidosValueMaskObject, gSLiM_GenomicElement_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); diff --git a/core/chromosome.h b/core/chromosome.h index 380034c3..522c8a98 100644 --- a/core/chromosome.h +++ b/core/chromosome.h @@ -809,7 +809,7 @@ class Chromosome_Class : public EidosDictionaryRetained_Class Chromosome_Class& operator=(const Chromosome_Class&) = delete; // no copying inline Chromosome_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/core/community.h b/core/community.h index 8ed4f1dd..92de402d 100644 --- a/core/community.h +++ b/core/community.h @@ -406,7 +406,7 @@ class Community_Class : public EidosDictionaryUnretained_Class Community_Class& operator=(const Community_Class&) = delete; // no copying inline Community_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index 66c20047..8603f426 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -1397,7 +1397,7 @@ EidosValue_SP Community::ExecuteMethod_usage(EidosGlobalStringID p_method_id, co Community_Class *gSLiM_Community_Class = nullptr; -const std::vector *Community_Class::Properties(void) const +std::vector *Community_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -1405,7 +1405,7 @@ const std::vector *Community_Class::Properties(void) { THREAD_SAFETY_IN_ANY_PARALLEL("Community_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_allGenomicElementTypes, true, kEidosValueMaskObject, gSLiM_GenomicElementType_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_allInteractionTypes, true, kEidosValueMaskObject, gSLiM_InteractionType_Class))); diff --git a/core/genomic_element.cpp b/core/genomic_element.cpp index f2f217da..42d51472 100644 --- a/core/genomic_element.cpp +++ b/core/genomic_element.cpp @@ -218,7 +218,7 @@ EidosValue_SP GenomicElement::ExecuteMethod_setGenomicElementType(EidosGlobalStr GenomicElement_Class *gSLiM_GenomicElement_Class = nullptr; -const std::vector *GenomicElement_Class::Properties(void) const +std::vector *GenomicElement_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -226,7 +226,7 @@ const std::vector *GenomicElement_Class::Properties( { THREAD_SAFETY_IN_ANY_PARALLEL("GenomicElement_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_genomicElementType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_GenomicElementType_Class))->DeclareAcceleratedGet(GenomicElement::GetProperty_Accelerated_genomicElementType)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_startPosition, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(GenomicElement::GetProperty_Accelerated_startPosition)); diff --git a/core/genomic_element.h b/core/genomic_element.h index 9eef739d..bd510998 100644 --- a/core/genomic_element.h +++ b/core/genomic_element.h @@ -96,7 +96,7 @@ class GenomicElement_Class : public EidosClass GenomicElement_Class& operator=(const GenomicElement_Class&) = delete; // no copying inline GenomicElement_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/core/genomic_element_type.cpp b/core/genomic_element_type.cpp index 8f175932..32fd7ce7 100644 --- a/core/genomic_element_type.cpp +++ b/core/genomic_element_type.cpp @@ -460,7 +460,7 @@ EidosValue_SP GenomicElementType::ExecuteMethod_setMutationMatrix(EidosGlobalStr GenomicElementType_Class *gSLiM_GenomicElementType_Class = nullptr; -const std::vector *GenomicElementType_Class::Properties(void) const +std::vector *GenomicElementType_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -468,7 +468,7 @@ const std::vector *GenomicElementType_Class::Propert { THREAD_SAFETY_IN_ANY_PARALLEL("GenomicElementType_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(GenomicElementType::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationTypes, true, kEidosValueMaskObject, gSLiM_MutationType_Class))); diff --git a/core/genomic_element_type.h b/core/genomic_element_type.h index a478f166..fb3a7a4a 100644 --- a/core/genomic_element_type.h +++ b/core/genomic_element_type.h @@ -120,7 +120,7 @@ class GenomicElementType_Class : public EidosDictionaryUnretained_Class GenomicElementType_Class& operator=(const GenomicElementType_Class&) = delete; // no copying inline GenomicElementType_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 441bfe37..fc80e1fe 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2237,7 +2237,7 @@ size_t Haplosome::MemoryUsageForMutrunBuffers(void) Haplosome_Class *gSLiM_Haplosome_Class = nullptr; -const std::vector *Haplosome_Class::Properties(void) const +std::vector *Haplosome_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -2245,7 +2245,7 @@ const std::vector *Haplosome_Class::Properties(void) { THREAD_SAFETY_IN_ANY_PARALLEL("Haplosome_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosome, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosomeSubposition, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Haplosome::GetProperty_Accelerated_chromosomeSubposition)); diff --git a/core/haplosome.h b/core/haplosome.h index 1691a483..911439ef 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -475,7 +475,7 @@ class Haplosome_Class : public EidosClass Haplosome_Class& operator=(const Haplosome_Class&) = delete; // no copying inline Haplosome_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; virtual EidosValue_SP ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const override; diff --git a/core/individual.cpp b/core/individual.cpp index a5c50ff5..83a365fd 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -1777,16 +1777,30 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: { - // Here we implement a special behavior: you can do individual. to access a trait value directly. + // Here we implement a special behavior: you can do individual. or individual.Offset + // to access an individual's trait value or trait offset directly. // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = subpopulation_->species_; Trait *trait = species.TraitFromStringID(p_property_id); - if (trait) + if (trait) // ACCELERATED { return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[trait->Index()].phenotype_)); } + else + { + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); + + if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Offset")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 6); + trait = species.TraitFromName(trait_name); + + if (trait) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[trait->Index()].offset_)); + } + } return super::GetProperty(p_property_id); } @@ -2619,6 +2633,7 @@ EidosValue *Individual::GetProperty_Accelerated_haplosomesNonNull(EidosGlobalStr EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) { #pragma unused (p_property_id) + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); const Individual **individuals_buffer = (const Individual **)p_values; Species *species = Community::SpeciesForIndividualsVector(individuals_buffer, (int)p_values_size); @@ -2626,6 +2641,10 @@ EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID if (species) { Trait *trait = species->TraitFromStringID(p_property_id); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): property " << property_string << " is not defined for object element type Individual in species " << species->name_ << "; trait " << property_string << " does not exist for this species." << EidosTerminate(); + slim_trait_index_t trait_index = trait->Index(); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -2641,7 +2660,12 @@ EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID for (size_t value_index = 0; value_index < p_values_size; ++value_index) { const Individual *value = individuals_buffer[value_index]; - Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); + Species &value_species = value->subpopulation_->species_; + Trait *trait = value_species.TraitFromStringID(p_property_id); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): property " << property_string << " is not defined for object element type Individual in species " << value_species.name_ << "; trait " << property_string << " does not exist for this species." << EidosTerminate(); + slim_trait_index_t trait_index = trait->Index(); float_result->set_float_no_check((double)value->trait_info_[trait_index].phenotype_, value_index); @@ -2651,6 +2675,52 @@ EidosValue *Individual::GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID return float_result; } +EidosValue *Individual::GetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size) +{ +#pragma unused (p_property_id) + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(p_values_size); + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); + std::string trait_name = property_string.substr(0, property_string.length() - 6); + const Individual **individuals_buffer = (const Individual **)p_values; + Species *species = Community::SpeciesForIndividualsVector(individuals_buffer, (int)p_values_size); + + if (species) + { + Trait *trait = species->TraitFromName(trait_name); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): property " << property_string << " is not defined for object element type Individual in species " << species->name_ << "; trait " << trait_name << " does not exist for this species." << EidosTerminate(); + + slim_trait_index_t trait_index = trait->Index(); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + + float_result->set_float_no_check((double)value->trait_info_[trait_index].offset_, value_index); + } + } + else + { + // with a mixed-species target, the species and trait have to be looked up for each individual + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + Species &value_species = value->subpopulation_->species_; + Trait *trait = value_species.TraitFromName(trait_name); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::GetProperty): property " << property_string << " is not defined for object element type Individual in species " << value_species.name_ << "; trait " << trait_name << " does not exist for this species." << EidosTerminate(); + + slim_trait_index_t trait_index = trait->Index(); + + float_result->set_float_no_check((double)value->trait_info_[trait_index].offset_, value_index); + } + } + + return float_result; +} + void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) { // All of our strings are in the global registry, so we can require a successful lookup @@ -2770,17 +2840,40 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue // all others, including gID_none default: { - // Here we implement a special behavior: you can do individual. to access a trait value directly. + // Here we implement a special behavior: you can do individual. or individual.Offset + // to access an individual's trait value or trait offset directly. // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = subpopulation_->species_; Trait *trait = species.TraitFromStringID(p_property_id); + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); if (trait) // ACCELERATED { - trait_info_[trait->Index()].phenotype_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + slim_effect_t new_phenotype = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + + if (std::isinf(new_phenotype)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); + + trait_info_[trait->Index()].phenotype_ = new_phenotype; return; } + else if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Offset")) // ACCELERATED + { + std::string trait_name = property_string.substr(0, property_string.length() - 6); + trait = species.TraitFromName(trait_name); + + if (trait) + { + slim_effect_t new_offset = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(new_offset)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite." << EidosTerminate(); + + trait_info_[trait->Index()].offset_ = new_offset; + return; + } + } return super::SetProperty(p_property_id, p_value); } @@ -3192,21 +3285,29 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope const Individual **individuals_buffer = (const Individual **)p_values; Species *species = Community::SpeciesForIndividualsVector(individuals_buffer, (int)p_values_size); const double *source_data = p_source.FloatData(); + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); if (species) { Trait *trait = species->TraitFromStringID(p_property_id); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is not defined for object element type Individual in species " << species->name_ << "; trait " << property_string << " does not exist for this species." << EidosTerminate(); + slim_trait_index_t trait_index = trait->Index(); if (p_source_size == 1) { - slim_effect_t source_value = (slim_effect_t)source_data[0]; + slim_effect_t new_phenotype = (slim_effect_t)source_data[0]; + + if (std::isinf(new_phenotype)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); for (size_t value_index = 0; value_index < p_values_size; ++value_index) { const Individual *value = individuals_buffer[value_index]; - value->trait_info_[trait_index].phenotype_ = source_value; + value->trait_info_[trait_index].phenotype_ = new_phenotype; } } else @@ -3214,8 +3315,12 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope for (size_t value_index = 0; value_index < p_values_size; ++value_index) { const Individual *value = individuals_buffer[value_index]; + slim_effect_t new_phenotype = (slim_effect_t)source_data[value_index]; + + if (std::isinf(new_phenotype)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); - value->trait_info_[trait_index].phenotype_ = (slim_effect_t)source_data[value_index]; + value->trait_info_[trait_index].phenotype_ = new_phenotype; } } } @@ -3224,15 +3329,23 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope // with a mixed-species target, the species and trait have to be looked up for each individual if (p_source_size == 1) { - slim_effect_t source_value = (slim_effect_t)source_data[0]; + slim_effect_t new_phenotype = (slim_effect_t)source_data[0]; + + if (std::isinf(new_phenotype)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); for (size_t value_index = 0; value_index < p_values_size; ++value_index) { const Individual *value = individuals_buffer[value_index]; - Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); + Species &value_species = value->subpopulation_->species_; + Trait *trait = value_species.TraitFromStringID(p_property_id); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is not defined for object element type Individual in species " << value_species.name_ << "; trait " << property_string << " does not exist for this species." << EidosTerminate(); + slim_trait_index_t trait_index = trait->Index(); - value->trait_info_[trait_index].phenotype_ = source_value; + value->trait_info_[trait_index].phenotype_ = new_phenotype; } } else @@ -3240,10 +3353,112 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope for (size_t value_index = 0; value_index < p_values_size; ++value_index) { const Individual *value = individuals_buffer[value_index]; - Trait *trait = value->subpopulation_->species_.TraitFromStringID(p_property_id); + Species &value_species = value->subpopulation_->species_; + Trait *trait = value_species.TraitFromStringID(p_property_id); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is not defined for object element type Individual in species " << value_species.name_ << "; trait " << property_string << " does not exist for this species." << EidosTerminate(); + slim_trait_index_t trait_index = trait->Index(); + slim_effect_t new_phenotype = (slim_effect_t)source_data[value_index]; + + if (std::isinf(new_phenotype)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); - value->trait_info_[trait_index].phenotype_ = (slim_effect_t)source_data[value_index]; + value->trait_info_[trait_index].phenotype_ = new_phenotype; + } + } + } +} + +void Individual::SetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size) +{ +#pragma unused (p_property_id) + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); + std::string trait_name = property_string.substr(0, property_string.length() - 6); + const Individual **individuals_buffer = (const Individual **)p_values; + Species *species = Community::SpeciesForIndividualsVector(individuals_buffer, (int)p_values_size); + const double *source_data = p_source.FloatData(); + + if (species) + { + Trait *trait = species->TraitFromName(trait_name); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is not defined for object element type Individual in species " << species->name_ << "; trait " << trait_name << " does not exist for this species." << EidosTerminate(); + + slim_trait_index_t trait_index = trait->Index(); + + if (p_source_size == 1) + { + slim_effect_t new_offset = (slim_effect_t)source_data[0]; + + if (!std::isfinite(new_offset)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + + value->trait_info_[trait_index].offset_ = new_offset; + } + } + else + { + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + slim_effect_t new_offset = (slim_effect_t)source_data[value_index]; + + if (!std::isfinite(new_offset)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); + + value->trait_info_[trait_index].offset_ = new_offset; + } + } + } + else + { + // with a mixed-species target, the species and trait have to be looked up for each individual + if (p_source_size == 1) + { + slim_effect_t new_offset = (slim_effect_t)source_data[0]; + + if (!std::isfinite(new_offset)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + Species &value_species = value->subpopulation_->species_; + Trait *trait = value_species.TraitFromName(trait_name); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is not defined for object element type Individual in species " << value_species.name_ << "; trait " << trait_name << " does not exist for this species." << EidosTerminate(); + + slim_trait_index_t trait_index = trait->Index(); + + value->trait_info_[trait_index].offset_ = new_offset; + } + } + else + { + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + Species &value_species = value->subpopulation_->species_; + Trait *trait = value_species.TraitFromName(trait_name); + + if (!trait) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is not defined for object element type Individual in species " << value_species.name_ << "; trait " << trait_name << " does not exist for this species." << EidosTerminate(); + + slim_trait_index_t trait_index = trait->Index(); + slim_effect_t new_offset = (slim_effect_t)source_data[value_index]; + + if (!std::isfinite(new_offset)) + EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); + + value->trait_info_[trait_index].offset_ = new_offset; } } } @@ -4302,7 +4517,7 @@ EidosValue_SP Individual::ExecuteMethod_mutationsFromHaplosomes(EidosGlobalStrin Individual_Class *gSLiM_Individual_Class = nullptr; -const std::vector *Individual_Class::Properties(void) const +std::vector *Individual_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -4310,7 +4525,7 @@ const std::vector *Individual_Class::Properties(void { THREAD_SAFETY_IN_ANY_PARALLEL("Individual_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopulation, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Subpopulation_Class))->DeclareAcceleratedGet(Individual::GetProperty_Accelerated_subpopulation)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_index, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Individual::GetProperty_Accelerated_index)); @@ -4479,6 +4694,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin // pattern 2: setting a single offset value across one or more traits in one or more individuals slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(0, nullptr)); + if (!std::isfinite(offset)) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); + for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; @@ -4502,6 +4720,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin Trait *trait = species->Traits()[trait_index]; slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(offset_index++, nullptr)); + if (!std::isfinite(offset)) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); + // effects for multiplicative traits are clamped to a minimum of 0.0 if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) offset = 0.0; @@ -4589,6 +4810,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { slim_effect_t offset = static_cast(*(offsets_float++)); + if (!std::isfinite(offset)) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); + // effects for multiplicative traits are clamped to a minimum of 0.0 if (offset < (slim_effect_t)0.0) offset = 0.0; @@ -4602,6 +4826,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { slim_effect_t offset = static_cast(*(offsets_float++)); + if (!std::isfinite(offset)) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); + individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; } } @@ -4617,6 +4844,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin Trait *trait = species->Traits()[trait_index]; slim_effect_t offset = static_cast(*(offsets_float++)); + if (!std::isfinite(offset)) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); + // effects for multiplicative traits are clamped to a minimum of 0.0 if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) offset = 0.0; @@ -4665,6 +4895,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt // pattern 1: setting a single phenotype value across one or more traits in one or more individuals slim_effect_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(0, nullptr)); + if (std::isinf(phenotype)) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires phenotypes to be finite or NAN." << EidosTerminate(); + if (trait_count == 1) { // optimized case for one trait @@ -4693,6 +4926,9 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt { slim_effect_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(phenotype_index++, nullptr)); + if (std::isinf(phenotype)) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires phenotypes to be finite or NAN." << EidosTerminate(); + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { Individual *ind = individuals_buffer[individual_index]; @@ -4740,7 +4976,14 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt slim_trait_index_t trait_index = trait_indices[0]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_float++)); + { + slim_effect_t phenotype = static_cast(*(phenotypes_float++)); + + if (std::isinf(phenotype)) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires phenotypes to be finite or NAN." << EidosTerminate(); + + individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = phenotype; + } } else { @@ -4749,7 +4992,14 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt Individual *ind = individuals_buffer[individual_index]; for (slim_trait_index_t trait_index : trait_indices) - ind->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_float++)); + { + slim_effect_t phenotype = static_cast(*(phenotypes_float++)); + + if (std::isinf(phenotype)) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires phenotypes to be finite or NAN." << EidosTerminate(); + + ind->trait_info_[trait_index].phenotype_ = phenotype; + } } } } diff --git a/core/individual.h b/core/individual.h index babbbf32..01a02593 100644 --- a/core/individual.h +++ b/core/individual.h @@ -377,6 +377,7 @@ class Individual : public EidosDictionaryUnretained static EidosValue *GetProperty_Accelerated_haplosomes(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_haplosomesNonNull(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); static EidosValue *GetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); + static EidosValue *GetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); // Accelerated property writing; see class EidosObject for comments on this mechanism static void SetProperty_Accelerated_tag(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); @@ -395,6 +396,7 @@ class Individual : public EidosDictionaryUnretained static void SetProperty_Accelerated_color(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); static void SetProperty_Accelerated_age(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); static void SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); + static void SetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size, const EidosValue &p_source, size_t p_source_size); // These flags are used to minimize the work done by Subpopulation::SwapChildAndParentHaplosomes(); it only needs to // reset colors or dictionaries if they have ever been touched by the model. These flags are set and never cleared. @@ -445,7 +447,7 @@ class Individual_Class : public EidosDictionaryUnretained_Class Individual_Class& operator=(const Individual_Class&) = delete; // no copying inline Individual_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; virtual EidosValue_SP ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const override; diff --git a/core/interaction_type.cpp b/core/interaction_type.cpp index baa870c3..3f117252 100755 --- a/core/interaction_type.cpp +++ b/core/interaction_type.cpp @@ -6508,7 +6508,7 @@ EidosValue_SP InteractionType::ExecuteMethod_unevaluate(EidosGlobalStringID p_me InteractionType_Class *gSLiM_InteractionType_Class = nullptr; -const std::vector *InteractionType_Class::Properties(void) const +std::vector *InteractionType_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -6516,7 +6516,7 @@ const std::vector *InteractionType_Class::Properties { THREAD_SAFETY_IN_ANY_PARALLEL("InteractionType_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(InteractionType::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_reciprocal, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))); diff --git a/core/interaction_type.h b/core/interaction_type.h index 5dce963e..6afbde14 100644 --- a/core/interaction_type.h +++ b/core/interaction_type.h @@ -482,7 +482,7 @@ class InteractionType_Class : public EidosDictionaryUnretained_Class InteractionType_Class& operator=(const InteractionType_Class&) = delete; // no copying inline InteractionType_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { InteractionType::_WarmUp(); } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/core/log_file.cpp b/core/log_file.cpp index 74bc6ab8..0e847fdc 100644 --- a/core/log_file.cpp +++ b/core/log_file.cpp @@ -1135,7 +1135,7 @@ EidosValue_SP LogFile::ExecuteMethod_setValue(EidosGlobalStringID p_method_id, c LogFile_Class *gSLiM_LogFile_Class = nullptr; -const std::vector *LogFile_Class::Properties(void) const +std::vector *LogFile_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -1143,7 +1143,7 @@ const std::vector *LogFile_Class::Properties(void) c { THREAD_SAFETY_IN_ANY_PARALLEL("LogFile_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_filePath, true, kEidosValueMaskString | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_logInterval, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); diff --git a/core/log_file.h b/core/log_file.h index b88218a0..5d599f93 100644 --- a/core/log_file.h +++ b/core/log_file.h @@ -187,7 +187,7 @@ class LogFile_Class : public EidosDictionaryRetained_Class LogFile_Class& operator=(const LogFile_Class &) = delete; // no copying inline LogFile_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; // Overrides of Dictionary methods, since we have a special Dictionary behavior diff --git a/core/mutation.cpp b/core/mutation.cpp index 184952d9..4a8213f5 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -1197,7 +1197,7 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & slim_effect_t new_effect = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); if (!std::isfinite(new_effect)) - EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << property_string << " is required to be finite." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); SetEffect(trait, traitInfoRec, new_effect); #if DEBUG @@ -1225,7 +1225,7 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); if (!std::isfinite(new_dominance)) - EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << new_dominance << " is required to be finite or NAN." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); SetHemizygousDominance(trait, traitInfoRec, new_dominance); #if DEBUG @@ -1253,7 +1253,7 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & slim_effect_t new_dominance = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); if (std::isinf(new_dominance)) - EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << new_dominance << " is required to be finite or NAN." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); SetDominance(trait, traitInfoRec, new_dominance); #if DEBUG @@ -1517,7 +1517,7 @@ EidosValue_SP Mutation::ExecuteMethod_setMutationType(EidosGlobalStringID p_meth Mutation_Class *gSLiM_Mutation_Class = nullptr; -const std::vector *Mutation_Class::Properties(void) const +std::vector *Mutation_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -1525,7 +1525,7 @@ const std::vector *Mutation_Class::Properties(void) { THREAD_SAFETY_IN_ANY_PARALLEL("Mutation_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosome, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_id)); @@ -1792,7 +1792,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { #pragma unused (p_method_id, p_interpreter) - const char *method_name = (p_method_id == gID_setDominanceForTrait) ? "setDominanceForTrait" : "setHemizygousDominanceForTrait"; + const char *method_name = (p_method_id == gID_setDominanceForTrait) ? "setDominanceForTrait()" : "setHemizygousDominanceForTrait()"; EidosValue *trait_value = p_arguments[0].get(); EidosValue *dominance_value = p_arguments[1].get(); diff --git a/core/mutation.h b/core/mutation.h index 876714f8..2656a75a 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -245,7 +245,7 @@ class Mutation_Class : public EidosDictionaryRetained_Class Mutation_Class& operator=(const Mutation_Class&) = delete; // no copying inline Mutation_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; virtual EidosValue_SP ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const override; diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index d089aed4..c43e102e 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -1453,7 +1453,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba // still want to let the species know that a mutation type has changed, though. species_.AutogenerationConfigurationChanged(); - SelfConsistencyCheck(" in setDefaultDominanceForTrait()"); + SelfConsistencyCheck(" after setDefaultDominanceForTrait()"); return gStaticEidosValueVOID; } @@ -1503,7 +1503,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( // still want to let the species know that a mutation type has changed, though. species_.AutogenerationConfigurationChanged(); - SelfConsistencyCheck(" in setDefaultHemizygousDominanceForTrait()"); + SelfConsistencyCheck(" after setDefaultHemizygousDominanceForTrait()"); return gStaticEidosValueVOID; } @@ -1555,7 +1555,7 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo MutationType_Class *gSLiM_MutationType_Class = nullptr; -const std::vector *MutationType_Class::Properties(void) const +std::vector *MutationType_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -1563,7 +1563,7 @@ const std::vector *MutationType_Class::Properties(vo { THREAD_SAFETY_IN_ANY_PARALLEL("MutationType_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(MutationType::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_convertToSubstitution, false, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedSet(MutationType::SetProperty_Accelerated_convertToSubstitution)); diff --git a/core/mutation_type.h b/core/mutation_type.h index 59d6e4ec..222e53ed 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -301,7 +301,7 @@ class MutationType_Class : public EidosDictionaryUnretained_Class MutationType_Class& operator=(const MutationType_Class&) = delete; // no copying inline MutationType_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index f187990a..428ca3cf 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -1847,7 +1847,7 @@ void SLiMEidosBlock::SetProperty(EidosGlobalStringID p_property_id, const EidosV SLiMEidosBlock_Class *gSLiM_SLiMEidosBlock_Class = nullptr; -const std::vector *SLiMEidosBlock_Class::Properties(void) const +std::vector *SLiMEidosBlock_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -1855,7 +1855,7 @@ const std::vector *SLiMEidosBlock_Class::Properties( { THREAD_SAFETY_IN_ANY_PARALLEL("SLiMEidosBlock_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_active, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); @@ -2081,9 +2081,11 @@ EidosTypeSpecifier SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(std: const std::string &traitEffect_name = trait_name + "Effect"; const std::string &traitDominance_name = trait_name + "Dominance"; const std::string &traitHemizygousDominance_name = trait_name + "HemizygousDominance"; + const std::string &traitOffset_name = trait_name + "Offset"; EidosPropertySignature_CSP species_trait_signature((new EidosPropertySignature(trait_name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP individual_trait_signature((new EidosPropertySignature(trait_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP individual_traitOffset_signature((new EidosPropertySignature(traitOffset_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP mutation_traitEffect_signature((new EidosPropertySignature(traitEffect_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP mutation_traitDominance_signature((new EidosPropertySignature(traitDominance_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP mutation_traitHemizygousDominance_signature((new EidosPropertySignature(traitHemizygousDominance_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); @@ -2093,6 +2095,7 @@ EidosTypeSpecifier SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(std: gSLiM_Species_Class->AddSignatureForProperty_TYPE_INTERPRETER(species_trait_signature); gSLiM_Individual_Class->AddSignatureForProperty_TYPE_INTERPRETER(individual_trait_signature); + gSLiM_Individual_Class->AddSignatureForProperty_TYPE_INTERPRETER(individual_traitOffset_signature); gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitEffect_signature); gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitDominance_signature); gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitHemizygousDominance_signature); diff --git a/core/slim_eidos_block.h b/core/slim_eidos_block.h index 030482f9..7cdf78b4 100644 --- a/core/slim_eidos_block.h +++ b/core/slim_eidos_block.h @@ -257,7 +257,7 @@ class SLiMEidosBlock_Class : public EidosClass SLiMEidosBlock_Class& operator=(const SLiMEidosBlock_Class&) = delete; // no copying inline SLiMEidosBlock_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead }; #ifdef EIDOS_GUI diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 29981816..3079e62f 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1067,6 +1067,8 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.baselineOffset, 186.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.baselineOffset = 12.5; if (!identical(T_height.baselineOffset, 12.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.baselineOffset = 17.25; if (!identical(T_weight.baselineOffset, 17.25)) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.baselineOffset = NAN; }", "requires a finite value", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.baselineOffset = INF; }", "requires a finite value", __LINE__); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.directFitnessEffect, F)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.directFitnessEffect, F)) stop(); }"); @@ -1078,11 +1080,15 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.individualOffsetMean, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; if (!identical(T_height.individualOffsetMean, 3.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; if (!identical(T_weight.individualOffsetMean, 2.5)) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.individualOffsetMean = NAN; }", "requires a finite value", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.individualOffsetMean = INF; }", "requires a finite value", __LINE__); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.individualOffsetSD, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.individualOffsetSD, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetSD = 3.5; if (!identical(T_height.individualOffsetSD, 3.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetSD = 2.5; if (!identical(T_weight.individualOffsetSD, 2.5)) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.individualOffsetSD = NAN; }", "requires a finite value", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.individualOffsetSD = INF; }", "requires a finite value", __LINE__); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.name, 'height')) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.name, 'weight')) stop(); }"); @@ -1111,6 +1117,10 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(0,1), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(NULL), 1:10 + 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1.0:10)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(c(1,0), 1:10 + 0.5); if (!identical(p1.individuals.offsetForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, NAN); }", "offset values to be finite", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, c(1,NAN,3,NAN,5)); }", "offset values to be finite", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, INF); }", "offset values to be finite", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, c(1,INF,3,INF,5)); }", "offset values to be finite", __LINE__); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_height), rep(3.5, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_weight), rep(2.5, 5))) stop(); }"); @@ -1128,20 +1138,37 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(c(0,1), 1:10 + 0.5); if (!identical(p1.individuals.phenotypeForTrait(NULL), 1:10 + 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(c(1,0), 1:10); if (!identical(p1.individuals.phenotypeForTrait(c(1,0)), 1.0:10)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(c(1,0), 1:10 + 0.5); if (!identical(p1.individuals.phenotypeForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(0, NAN); p1.individuals.setPhenotypeForTrait(1, 4.5); if (!identical(p1.individuals.phenotypeForTrait(NULL), rep(c(NAN, 4.5), 5))) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.setPhenotypeForTrait(0, INF); }", "phenotypes to be finite or NAN", __LINE__); - // species trait property access + // Species property access SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(sim.height, sim.traitsWithNames('height'))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(sim.weight, sim.traitsWithNames('weight'))) stop(); }"); SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.height = sim.traitsWithNames('height'); }", "new value for read-only property", __LINE__); SLiMAssertScriptRaise(mt_base_p1 + "1 late() { sim.weight = sim.traitsWithNames('weight'); }", "new value for read-only property", __LINE__); - // individual trait property access + // Individual property access SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.height, rep(NAN, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.weight, rep(NAN, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0; if (!identical(p1.individuals.height, rep(10.0, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 10.0; if (!identical(p1.individuals.weight, rep(10.0, 5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0:14; if (!identical(p1.individuals.height, 10.0:14)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 11.0:15; if (!identical(p1.individuals.weight, 11.0:15)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = NAN; if (!identical(p1.individuals.height, rep(NAN, 5))) stop(); }"); // NAN means uncalculated; you can set that + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = c(10.0,NAN,12,NAN,14); if (!identical(p1.individuals.height, c(10.0,NAN,12,NAN,14))) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.height = INF; }", "required to be finite or NAN", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.height = c(10.0,INF,12,INF,14); }", "required to be finite or NAN", __LINE__); + + // Individual Offset property access + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.heightOffset, rep(1.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.weightOffset, rep(0.0, 5))) stop(); }"); + + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.heightOffset = 3.0; p1.individuals.weightOffset = 4.5; if (!identical(p1.individuals.offsetForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.heightOffset = 1.0:5 * 2 - 1; p1.individuals.weightOffset = 1.0:5 * 2; if (!identical(p1.individuals.offsetForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.heightOffset = NAN; }", "required to be a finite value (not INF or NAN)", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.heightOffset = c(1,NAN,3,NAN,5); }", "required to be a finite value (not INF or NAN)", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.heightOffset = INF; }", "required to be a finite value (not INF or NAN)", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.heightOffset = c(1,INF,3,INF,5); }", "required to be a finite value (not INF or NAN)", __LINE__); // Mutation effectForTrait() SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectForTrait(0), 0.0)) stop(); }"); @@ -1157,6 +1184,10 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(0,1), 1:10 + 0.5); if (!identical(mut.effectForTrait(NULL), 1:10 + 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(1,0), 1:10); if (!identical(mut.effectForTrait(c(1,0)), 1.0:10)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.effectForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, NAN); }", "non-finite after setEffectForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, c(1,NAN,3,NAN,5)); }", "non-finite after setEffectForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, INF); }", "non-finite after setEffectForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, c(1,INF,3,INF,5)); }", "non-finite after setEffectForTrait()", __LINE__); // Mutation dominanceForTrait() SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(0), 0.5)) stop(); }"); @@ -1172,6 +1203,9 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(0,1), 1:10 + 0.5); if (!identical(mut.dominanceForTrait(NULL), 1:10 + 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(1,0), 1:10); if (!identical(mut.dominanceForTrait(c(1,0)), 1.0:10)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.dominanceForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + // NAN for dominance means independent dominance, so it is legal; tested below + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(0, INF); }", "infinite after setDominanceForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setDominanceForTrait(0, c(1,INF,3,INF,5)); }", "infinite after setDominanceForTrait()", __LINE__); // MutationType defaultDominanceForTrait() and setDefaultDominanceForTrait() SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultDominanceForTrait(0), 0.5)) stop(); } 5 late() { }"); @@ -1183,6 +1217,9 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(c(0,1), c(0.25, 1.0)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(c(0,1)), c(0.25, 1.0))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(c(1,0), c(0.25, 1.0)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(c(1,0)), c(0.25, 1.0))) stop(); }"); SLiMAssertScriptStop(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(c(1,0), c(0.25, 1.0)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(c(0,1)), c(0.25, 1.0))) stop(); }"); + // NAN for dominance means independent dominance, so it is legal; tested below + SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(0, INF); }", "infinite after setDefaultDominanceForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultDominanceForTrait(NULL, c(0.25, INF)); }", "infinite after setDefaultDominanceForTrait()", __LINE__); // Mutation hemizygousDominanceForTrait() SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(0), 1.0)) stop(); }"); @@ -1198,6 +1235,11 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(c(0,1), 1:10 + 0.5); if (!identical(mut.hemizygousDominanceForTrait(NULL), 1:10 + 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(c(1,0), 1:10); if (!identical(mut.hemizygousDominanceForTrait(c(1,0)), 1.0:10)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.hemizygousDominanceForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + // hemizygous dominance cannot be NAN, since independent dominance has no meaning for it + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(0, NAN); }", "non-finite after setHemizygousDominanceForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(0, c(1,NAN,3,NAN,5)); }", "non-finite after setHemizygousDominanceForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(0, INF); }", "non-finite after setHemizygousDominanceForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(0, c(1,INF,3,INF,5)); }", "non-finite after setHemizygousDominanceForTrait()", __LINE__); // MutationType defaultHemizygousDominanceForTrait() and setDefaultHemizygousDominanceForTrait() SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultHemizygousDominanceForTrait(0), 1.0)) stop(); } 5 late() { }"); @@ -1209,6 +1251,11 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(c(0,1), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(0,1)), c(0.25, 0.5))) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(c(1,0), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(1,0)), c(0.25, 0.5))) stop(); }"); SLiMAssertScriptStop(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(c(1,0), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(0,1)), c(0.25, 0.5))) stop(); }"); + // hemizygous dominance cannot be NAN, since independent dominance has no meaning for it + SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(0, NAN); }", "non-finite after setDefaultHemizygousDominanceForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(NULL, c(0.25, NAN)); }", "non-finite after setDefaultHemizygousDominanceForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(0, INF); }", "non-finite after setDefaultHemizygousDominanceForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(NULL, c(0.25, INF)); }", "non-finite after setDefaultHemizygousDominanceForTrait()", __LINE__); // Substitution effectForTrait() SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(0), 0.0)) stop(); }"); @@ -1230,18 +1277,25 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightEffect, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffect = 0.25; if (!identical(mut.heightEffect, 0.25)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightEffect = 0.25; if (!identical(mut.weightEffect, 0.25)) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffect = NAN; }", "required to be a finite value (not INF or NAN)", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffect = INF; }", "required to be a finite value (not INF or NAN)", __LINE__); // Mutation Dominance property SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightDominance, 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightDominance, 0.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightDominance = 0.25; if (!identical(mut.heightDominance, 0.25)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightDominance = 0.25; if (!identical(mut.weightDominance, 0.25)) stop(); }"); + // NAN for dominance means independent dominance, so it is legal; tested below + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightDominance = INF; }", "required to be finite or NAN", __LINE__); // Mutation HemizygousDominance property SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightHemizygousDominance, 1.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightHemizygousDominance, 1.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightHemizygousDominance = 0.25; if (!identical(mut.heightHemizygousDominance, 0.25)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightHemizygousDominance = 0.25; if (!identical(mut.weightHemizygousDominance, 0.25)) stop(); }"); + // hemizygous dominance cannot be NAN, since independent dominance has no meaning for it + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightHemizygousDominance = NAN; }", "required to be finite or NAN", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightHemizygousDominance = INF; }", "required to be finite or NAN", __LINE__); // Substitution Effect property SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.heightEffect, 0.0)) stop(); }"); diff --git a/core/spatial_map.cpp b/core/spatial_map.cpp index 36c185e5..5895b7dd 100644 --- a/core/spatial_map.cpp +++ b/core/spatial_map.cpp @@ -3176,7 +3176,7 @@ static EidosValue_SP SLiM_Instantiate_SpatialMap(const std::vector *SpatialMap_Class::Properties(void) const +std::vector *SpatialMap_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -3184,7 +3184,7 @@ const std::vector *SpatialMap_Class::Properties(void { THREAD_SAFETY_IN_ANY_PARALLEL("SpatialMap_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_gridDimensions, true, kEidosValueMaskInt))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_interpolate, false, kEidosValueMaskLogical | kEidosValueMaskSingleton))); diff --git a/core/spatial_map.h b/core/spatial_map.h index bb031219..0a2c98c4 100644 --- a/core/spatial_map.h +++ b/core/spatial_map.h @@ -200,7 +200,7 @@ class SpatialMap_Class : public EidosDictionaryRetained_Class SpatialMap_Class& operator=(const SpatialMap_Class&) = delete; // no copying inline SpatialMap_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; virtual const std::vector *Functions(void) const override; }; diff --git a/core/species.h b/core/species.h index 67fe930f..26744d31 100644 --- a/core/species.h +++ b/core/species.h @@ -793,7 +793,7 @@ class Species_Class : public EidosDictionaryUnretained_Class Species_Class& operator=(const Species_Class&) = delete; // no copying inline Species_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 636b99bc..e3083862 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1671,8 +1671,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string if (trait->Name() == name) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name is unique within the species; there is already a trait in this species with the name '" << name << "'." << EidosTerminate(); - if (Eidos_string_hasSuffix(name, "Effect") || Eidos_string_hasSuffix(name, "Dominance") || Eidos_string_hasSuffix(name, "Hemizygous")) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name does not end in 'Effect', 'Dominance', or 'Hemizygous' to avoid naming conflicts and general confusion." << EidosTerminate(); + if (Eidos_string_hasSuffix(name, "Effect") || Eidos_string_hasSuffix(name, "Dominance") || Eidos_string_hasSuffix(name, "Hemizygous") || Eidos_string_hasSuffix(name, "Offset")) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name does not end in 'Effect', 'Dominance', 'Hemizygous', or 'Offset' to avoid naming conflicts and general confusion." << EidosTerminate(); if (name == "NULL") EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() does not allow a trait name of 'NULL', to avoid naming conflicts and general confusion." << EidosTerminate(); @@ -1759,6 +1759,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string EidosGlobalStringID traitEffect_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Effect"); EidosGlobalStringID traitDominance_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Dominance"); EidosGlobalStringID traitHemizygousDominance_stringID = EidosStringRegistry::GlobalStringIDForString(name + "HemizygousDominance"); + EidosGlobalStringID traitOffset_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Offset"); { // add a Species property that returns the trait object @@ -1808,6 +1809,29 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } } + { + // add an Individual Offset property that returns the individual offset for the trait in an individual + const EidosPropertySignature *existing_signature = gSLiM_Individual_Class->SignatureForProperty(traitOffset_stringID); + + if (existing_signature) + { + if (!existing_signature->IsDynamicWithOwner("Trait") || + (existing_signature->value_mask_ != (kEidosValueMaskFloat | kEidosValueMaskSingleton)) || + (existing_signature->read_only_ == true)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() needs to register the trait name as a property in the Individual class, but the name '" << name << "' conflicts with an existing property on Individual. A different name must be used for this trait." << EidosTerminate(); + } + else + { + // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this + EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "Offset", false, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + MarkAsDynamicWithOwner("Trait")-> + DeclareAcceleratedGet(Individual::GetProperty_Accelerated_TRAIT_OFFSET)-> + DeclareAcceleratedSet(Individual::SetProperty_Accelerated_TRAIT_OFFSET)); + + gSLiM_Individual_Class->AddSignatureForProperty(signature); + } + } + { // add a Mutation Effect property that returns the effect size for the trait in a mutation const EidosPropertySignature *existing_signature = gSLiM_Mutation_Class->SignatureForProperty(traitEffect_stringID); @@ -4808,7 +4832,7 @@ EidosValue_SP Species::ExecuteMethod__debug(EidosGlobalStringID p_method_id, con Species_Class *gSLiM_Species_Class = nullptr; -const std::vector *Species_Class::Properties(void) const +std::vector *Species_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -4816,7 +4840,7 @@ const std::vector *Species_Class::Properties(void) c { THREAD_SAFETY_IN_ANY_PARALLEL("Species_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_avatar, true, kEidosValueMaskString | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosome, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class))); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index b5a47236..3b0c6090 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -11641,7 +11641,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_configureDisplay(EidosGlobalStringID Subpopulation_Class *gSLiM_Subpopulation_Class = nullptr; -const std::vector *Subpopulation_Class::Properties(void) const +std::vector *Subpopulation_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -11649,7 +11649,7 @@ const std::vector *Subpopulation_Class::Properties(v { THREAD_SAFETY_IN_ANY_PARALLEL("Subpopulation_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Subpopulation::GetProperty_Accelerated_id)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_firstMaleIndex, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Subpopulation::GetProperty_Accelerated_firstMaleIndex)); diff --git a/core/subpopulation.h b/core/subpopulation.h index 16a34a24..f4ac0cda 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -597,7 +597,7 @@ class Subpopulation_Class : public EidosDictionaryUnretained_Class Subpopulation_Class& operator=(const Subpopulation_Class&) = delete; // no copying inline Subpopulation_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/core/substitution.cpp b/core/substitution.cpp index c96e92c8..0407e97d 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -892,7 +892,7 @@ EidosValue_SP Substitution::ExecuteMethod_isIndependentDominanceForTrait(EidosGl Substitution_Class *gSLiM_Substitution_Class = nullptr; -const std::vector *Substitution_Class::Properties(void) const +std::vector *Substitution_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -900,7 +900,7 @@ const std::vector *Substitution_Class::Properties(vo { THREAD_SAFETY_IN_ANY_PARALLEL("Substitution_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_chromosome, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_id, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_id)); diff --git a/core/substitution.h b/core/substitution.h index ed851adb..bf4b84e8 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -142,7 +142,7 @@ class Substitution_Class : public EidosDictionaryRetained_Class Substitution_Class& operator=(const Substitution_Class&) = delete; // no copying inline Substitution_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/core/trait.cpp b/core/trait.cpp index 6e6d3dba..2ab213ea 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -240,7 +240,7 @@ EidosValue_SP Trait::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, cons Trait_Class *gSLiM_Trait_Class = nullptr; -const std::vector *Trait_Class::Properties(void) const +std::vector *Trait_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -248,7 +248,7 @@ const std::vector *Trait_Class::Properties(void) con { THREAD_SAFETY_IN_ANY_PARALLEL("Trait_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_baselineOffset, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_directFitnessEffect, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))); diff --git a/core/trait.h b/core/trait.h index 2b588e17..f7a98715 100644 --- a/core/trait.h +++ b/core/trait.h @@ -158,7 +158,7 @@ class Trait_Class : public EidosDictionaryRetained_Class Trait_Class& operator=(const Trait_Class&) = delete; // no copying inline Trait_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; }; diff --git a/eidos/eidos_class_DataFrame.cpp b/eidos/eidos_class_DataFrame.cpp index 616f943d..a27f0a49 100644 --- a/eidos/eidos_class_DataFrame.cpp +++ b/eidos/eidos_class_DataFrame.cpp @@ -1264,7 +1264,7 @@ static EidosValue_SP Eidos_ExecuteFunction_readCSV(const std::vector *EidosDataFrame_Class::Properties(void) const +std::vector *EidosDataFrame_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -1272,7 +1272,7 @@ const std::vector *EidosDataFrame_Class::Properties( { THREAD_SAFETY_IN_ANY_PARALLEL("EidosDataFrame_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_colNames, true, kEidosValueMaskString))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_dim, true, kEidosValueMaskInt))); diff --git a/eidos/eidos_class_DataFrame.h b/eidos/eidos_class_DataFrame.h index aa3d04fb..e7fe0ce3 100644 --- a/eidos/eidos_class_DataFrame.h +++ b/eidos/eidos_class_DataFrame.h @@ -101,7 +101,7 @@ class EidosDataFrame_Class : public EidosDictionaryRetained_Class EidosDataFrame_Class& operator=(const EidosDataFrame_Class&) = delete; // no copying inline EidosDataFrame_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; virtual const std::vector *Functions(void) const override; }; diff --git a/eidos/eidos_class_Dictionary.cpp b/eidos/eidos_class_Dictionary.cpp index e5412d24..a3d49fc8 100644 --- a/eidos/eidos_class_Dictionary.cpp +++ b/eidos/eidos_class_Dictionary.cpp @@ -1624,7 +1624,7 @@ EidosValue_SP EidosDictionaryUnretained::ExecuteMethod_serialize(EidosGlobalStri EidosDictionaryUnretained_Class *gEidosDictionaryUnretained_Class = nullptr; -const std::vector *EidosDictionaryUnretained_Class::Properties(void) const +std::vector *EidosDictionaryUnretained_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -1632,7 +1632,7 @@ const std::vector *EidosDictionaryUnretained_Class:: { THREAD_SAFETY_IN_ANY_PARALLEL("EidosDictionaryUnretained_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_allKeys, true, kEidosValueMaskInt | kEidosValueMaskString))); diff --git a/eidos/eidos_class_Dictionary.h b/eidos/eidos_class_Dictionary.h index fb211309..301975df 100644 --- a/eidos/eidos_class_Dictionary.h +++ b/eidos/eidos_class_Dictionary.h @@ -245,7 +245,7 @@ class EidosDictionaryUnretained_Class : public EidosClass inline EidosDictionaryUnretained_Class(const std::string &p_class_name, const std::string &p_display_name, EidosClass *p_superclass) : super(p_class_name, p_display_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; virtual EidosValue_SP ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const override; diff --git a/eidos/eidos_class_Image.cpp b/eidos/eidos_class_Image.cpp index 51462d79..2960649c 100644 --- a/eidos/eidos_class_Image.cpp +++ b/eidos/eidos_class_Image.cpp @@ -362,7 +362,7 @@ static EidosValue_SP Eidos_Instantiate_EidosImage(const std::vector *EidosImage_Class::Properties(void) const +std::vector *EidosImage_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -370,7 +370,7 @@ const std::vector *EidosImage_Class::Properties(void { THREAD_SAFETY_IN_ANY_PARALLEL("EidosImage_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_width, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr_height, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); diff --git a/eidos/eidos_class_Image.h b/eidos/eidos_class_Image.h index 0401e1a8..44a4c3b5 100644 --- a/eidos/eidos_class_Image.h +++ b/eidos/eidos_class_Image.h @@ -104,7 +104,7 @@ class EidosImage_Class : public EidosDictionaryRetained_Class EidosImage_Class& operator=(const EidosImage_Class&) = delete; // no copying inline EidosImage_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; virtual const std::vector *Functions(void) const override; }; diff --git a/eidos/eidos_class_Object.cpp b/eidos/eidos_class_Object.cpp index 86954466..6b8df71d 100644 --- a/eidos/eidos_class_Object.cpp +++ b/eidos/eidos_class_Object.cpp @@ -530,12 +530,14 @@ void EidosClass::RaiseForDispatchUninitialized(void) const void EidosClass::AddSignatureForProperty(EidosPropertySignature_CSP p_property_signature) { #if DEBUG - if (!dispatches_cached_) - RaiseForDispatchUninitialized(); + if (!dispatches_cached_) + RaiseForDispatchUninitialized(); #endif EidosGlobalStringID property_id = p_property_signature->property_id_; + std::string property_name = p_property_signature->property_name_; + // We need to add the property to the class's property dispatch table if (property_id < (EidosGlobalStringID)property_signatures_dispatch_capacity_) { // The property id fits into our existing dispatch table, so we can just fill it in. @@ -561,9 +563,37 @@ void EidosClass::AddSignatureForProperty(EidosPropertySignature_CSP p_property_s property_signatures_dispatch_[property_id] = p_property_signature; } + + // We also need to add it to the vector of property signatures kept by the class. We do this by + // simply calling Properties(), which returns a pointer to its internally allocated static vector + // of properties, and then modifying that vector. This is a pretty weird thing to do, and it is + // not as general as it ought to be; if we add a property to a superclass, it is not added to the + // vector of properties kept by a subclass, in the present design. But it will work for now. + // FIXME a more general design would be for each class to keep a mutable vector of its own properties, + // and then have an EidosClass method that walks up the superclass chain, assembles and uniques a + // vector of all methods, and sorts and returns that as a const vector, re-done on request every time. + // The Properties() method is no longer a bottleneck for much, so something like that would be maybe ok. + std::vector *properties = Properties_MUTABLE(); + + if (std::find_if(properties->begin(), properties->end(), + [property_name](EidosPropertySignature_CSP property_signature) { + return (property_signature->property_name_ == property_name); + }) == properties->end()) + { + properties->push_back(p_property_signature); + + std::sort(properties->begin(), properties->end(), CompareEidosPropertySignatures); + } } const std::vector *EidosClass::Properties(void) const +{ + // This just gets the mutable properties vector and returns it as a const properties vector instead. + // This is the accessor used by everything except adding dynamic properties to classes. + return Properties_MUTABLE(); +} + +std::vector *EidosClass::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; diff --git a/eidos/eidos_class_Object.h b/eidos/eidos_class_Object.h index 0541ce58..c588cb18 100644 --- a/eidos/eidos_class_Object.h +++ b/eidos/eidos_class_Object.h @@ -201,7 +201,12 @@ class EidosClass return nullptr; } - virtual const std::vector *Properties(void) const; + // Getting class information about supported properties, methods, and functions. Right now getting a mutable + // version of this information is only supported for properties, since we need to add dynamic properties to + // classes; the same could be done for methods and functions, but there is no need for it at present. + const std::vector *Properties(void) const; + virtual std::vector *Properties_MUTABLE(void) const; + virtual const std::vector *Methods(void) const; virtual const std::vector *Functions(void) const; diff --git a/eidos/eidos_class_TestElement.cpp b/eidos/eidos_class_TestElement.cpp index c5c3876e..275662e1 100644 --- a/eidos/eidos_class_TestElement.cpp +++ b/eidos/eidos_class_TestElement.cpp @@ -197,7 +197,7 @@ static EidosValue_SP Eidos_Instantiate_EidosTestElement(const std::vector *EidosTestElement_Class::Properties(void) const +std::vector *EidosTestElement_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -205,7 +205,7 @@ const std::vector *EidosTestElement_Class::Propertie { THREAD_SAFETY_IN_ANY_PARALLEL("EidosTestElement_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr__yolk, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(EidosTestElement::GetProperty_Accelerated__yolk)->DeclareAcceleratedSet(EidosTestElement::SetProperty_Accelerated__yolk)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr__increment, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gEidosTestElement_Class))); @@ -341,7 +341,7 @@ static EidosValue_SP Eidos_Instantiate_EidosTestElementNRR(const std::vector *EidosTestElementNRR_Class::Properties(void) const +std::vector *EidosTestElementNRR_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; @@ -349,7 +349,7 @@ const std::vector *EidosTestElementNRR_Class::Proper { THREAD_SAFETY_IN_ANY_PARALLEL("EidosTestElementNRR_Class::Properties(): not warmed up"); - properties = new std::vector(*super::Properties()); + properties = new std::vector(*super::Properties_MUTABLE()); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gEidosStr__yolk, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); diff --git a/eidos/eidos_class_TestElement.h b/eidos/eidos_class_TestElement.h index d3226611..1279b326 100644 --- a/eidos/eidos_class_TestElement.h +++ b/eidos/eidos_class_TestElement.h @@ -88,7 +88,7 @@ class EidosTestElement_Class : public EidosDictionaryRetained_Class EidosTestElement_Class& operator=(const EidosTestElement_Class&) = delete; // no copying inline EidosTestElement_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Methods(void) const override; virtual const std::vector *Functions(void) const override; }; @@ -136,7 +136,7 @@ class EidosTestElementNRR_Class : public EidosClass EidosTestElementNRR_Class& operator=(const EidosTestElementNRR_Class&) = delete; // no copying inline EidosTestElementNRR_Class(const std::string &p_class_name, EidosClass *p_superclass) : super(p_class_name, p_superclass) { } - virtual const std::vector *Properties(void) const override; + virtual std::vector *Properties_MUTABLE(void) const override; // use Properties() instead virtual const std::vector *Functions(void) const override; }; From 6132740c2443a1457e8e618a5c349e9a43916e3f Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 21 Jan 2026 16:26:53 -0600 Subject: [PATCH 086/107] fix CI self-test errors --- core/mutation.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/mutation.cpp b/core/mutation.cpp index 4a8213f5..5e57c92a 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -1778,10 +1778,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI else EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): setEffectForTrait() requires that effect be (a) NULL, requesting an effect value drawn from the mutation's mutation type for each trait, (b) singleton, providing one effect value for all traits, (c) equal in length to the number of traits in the species, providing one effect value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one effect value per trait per mutation." << EidosTerminate(); -#if DEBUG + // Check for problems; for setEffectForTrait(), it is easier to do sanity checks afterwards than up front for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) mutations_buffer[mutation_index]->SelfConsistencyCheck(" after setEffectForTrait()"); -#endif return gStaticEidosValueVOID; } @@ -2006,10 +2005,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri else EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_" << method_name << "): " << method_name << "() requires that dominance be (a) NULL, requesting the default" << ((p_method_id == gID_setDominanceForTrait) ? " " : " hemizygous ") << "dominance coefficient from the mutation's mutation type for each trait, (b) singleton, providing one dominance value for all traits, (c) equal in length to the number of traits in the species, providing one dominance value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one dominance value per trait per mutation." << EidosTerminate(); -#if DEBUG + // Check for problems; for setEffectForTrait(), it is easier to do sanity checks afterwards than up front for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) mutations_buffer[mutation_index]->SelfConsistencyCheck(std::string(" after ") + method_name); -#endif return gStaticEidosValueVOID; } From 4ae352dc13339c3bde816366ba781174b71f1a7d Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 21 Jan 2026 17:30:05 -0600 Subject: [PATCH 087/107] fix remaining CI issues, hopefully --- QtSLiM/QtSLiM_Plot.cpp | 2 +- QtSLiM/QtSLiM_SLiMgui.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/QtSLiM/QtSLiM_Plot.cpp b/QtSLiM/QtSLiM_Plot.cpp index 39129c28..bd83ad4b 100644 --- a/QtSLiM/QtSLiM_Plot.cpp +++ b/QtSLiM/QtSLiM_Plot.cpp @@ -1860,7 +1860,7 @@ EidosValue_SP Plot::ExecuteMethod_write(EidosGlobalStringID p_method_id, const s Plot_Class *gSLiM_Plot_Class = nullptr; -const std::vector *Plot_Class::Properties(void) const +std::vector *Plot_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; diff --git a/QtSLiM/QtSLiM_SLiMgui.cpp b/QtSLiM/QtSLiM_SLiMgui.cpp index 74207ac8..4740a7cf 100644 --- a/QtSLiM/QtSLiM_SLiMgui.cpp +++ b/QtSLiM/QtSLiM_SLiMgui.cpp @@ -299,7 +299,7 @@ EidosValue_SP SLiMgui::ExecuteMethod_plotWithTitle(EidosGlobalStringID p_method_ SLiMgui_Class *gSLiM_SLiMgui_Class = nullptr; -const std::vector *SLiMgui_Class::Properties(void) const +std::vector *SLiMgui_Class::Properties_MUTABLE(void) const { static std::vector *properties = nullptr; From b231828df19a507e0384266e4374422d7e0899d8 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 21 Jan 2026 21:08:57 -0600 Subject: [PATCH 088/107] terminology shift from "effect" to "effect size" for Mutation --- EidosScribe/EidosCocoaExtra.mm | 2 +- EidosScribe/EidosHelpController.mm | 4 +- QtSLiM/QtSLiMChromosomeWidget_GL.cpp | 2 +- QtSLiM/QtSLiMChromosomeWidget_QT.cpp | 2 +- QtSLiM/QtSLiMExtras.cpp | 2 +- QtSLiM/QtSLiMHelpWindow.cpp | 4 +- QtSLiM/QtSLiMTablesDrawer.cpp | 10 +- QtSLiM/QtSLiMWindow.cpp | 16 +- QtSLiM/help/SLiMHelpCallbacks.html | 2 +- QtSLiM/help/SLiMHelpClasses.html | 80 ++++----- QtSLiM/help/SLiMHelpFunctions.html | 4 +- SLiMgui/ChromosomeView.mm | 2 +- SLiMgui/CocoaExtra.mm | 6 +- SLiMgui/SLiMHelpCallbacks.rtf | 2 +- SLiMgui/SLiMHelpClasses.rtf | 144 ++++++++-------- SLiMgui/SLiMHelpFunctions.rtf | 17 +- SLiMgui/SLiMWindowController.mm | 2 +- VERSIONS | 17 +- core/haplosome.cpp | 42 ++--- core/individual.cpp | 12 +- core/mutation.cpp | 174 +++++++++---------- core/mutation.h | 6 +- core/mutation_type.cpp | 150 ++++++++--------- core/mutation_type.h | 36 ++-- core/slim_eidos_block.cpp | 10 +- core/slim_functions.cpp | 4 +- core/slim_globals.cpp | 13 +- core/slim_globals.h | 28 ++-- core/slim_test.cpp | 2 +- core/slim_test_genetics.cpp | 240 +++++++++++++-------------- core/slim_test_other.cpp | 2 +- core/species.cpp | 8 +- core/species_eidos.cpp | 20 +-- core/substitution.cpp | 32 ++-- core/substitution.h | 2 +- eidos/eidos_globals.h | 2 +- 36 files changed, 557 insertions(+), 544 deletions(-) diff --git a/EidosScribe/EidosCocoaExtra.mm b/EidosScribe/EidosCocoaExtra.mm index eb0971b5..7f3196d8 100644 --- a/EidosScribe/EidosCocoaExtra.mm +++ b/EidosScribe/EidosCocoaExtra.mm @@ -223,7 +223,7 @@ + (NSAttributedString *)eidosAttributedStringForPropertySignature:(const EidosPr [attrStr addAttribute:NSBaselineOffsetAttributeName value:[NSNumber numberWithFloat:2.0] range:NSMakeRange(0, [attrStr length])]; - // BCH 1/7/2025: For dynamic properties with a name like "Effect", italicize the portion in the <> + // BCH 1/7/2025: For dynamic properties with a name like "EffectSize", italicize the portion in the <> if ([propertyNameString hasPrefix:@"<"]) { NSRange endRange = [propertyNameString rangeOfString:@">"]; diff --git a/EidosScribe/EidosHelpController.mm b/EidosScribe/EidosHelpController.mm index de6b9c4d..85f5413f 100644 --- a/EidosScribe/EidosHelpController.mm +++ b/EidosScribe/EidosHelpController.mm @@ -544,7 +544,7 @@ - (void)addTopicsFromRTFFile:(NSString *)rtfFile underHeading:(NSString *)topLev if ([callName hasPrefix:@"<"]) { - // BCH 1/7/2026: This case is hit for dynamic properties, which are present in the doc with names like Effect. We don't want to + // BCH 1/7/2026: This case is hit for dynamic properties, which are present in the doc with names like EffectSize. We don't want to // check for a match, we just want to accept the property as it is. We do want to reformat it, though; we make a temporary signature for that. std::string property_name([callName UTF8String]); bool property_read_only = true; @@ -719,7 +719,7 @@ - (void)checkDocumentationOfClass:(EidosClass *)classObject } } - // BCH 1/7/2026: Remove dynamic properties like "Effect" from the excess documentation list; they are not expected to have a match + // BCH 1/7/2026: Remove dynamic properties like "EffectSize" from the excess documentation list; they are not expected to have a match [docProperties filterUsingPredicate:[NSPredicate predicateWithFormat:@"! SELF beginswith '<'"]]; if ([docProperties count]) diff --git a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp index 916c76fe..8c2c30f7 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_GL.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp @@ -261,7 +261,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch for (auto mutationTypeIter : mut_types) { MutationType *mut_type = mutationTypeIter.second; - EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + EffectSizeDistributionInfo &DES_info = mut_type->effect_size_distributions_[0]; // FIXME MULTITRAIT if (mut_type->mutation_type_displayed_) { diff --git a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp index ffdcec04..c7558650 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp @@ -263,7 +263,7 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch if (draw_muttypes_sequentially) { bool mut_type_fixed_color = !mut_type->color_.empty(); - EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + EffectSizeDistributionInfo &DES_info = mut_type->effect_size_distributions_[0]; // FIXME MULTITRAIT // We optimize fixed-DES mutation types only, and those using a fixed color set by the user if ((DES_info.DES_type_ == DESType::kFixed) || mut_type_fixed_color) diff --git a/QtSLiM/QtSLiMExtras.cpp b/QtSLiM/QtSLiMExtras.cpp index 097b22d9..1de5ed90 100644 --- a/QtSLiM/QtSLiMExtras.cpp +++ b/QtSLiM/QtSLiMExtras.cpp @@ -625,7 +625,7 @@ void ColorizePropertySignature(const EidosPropertySignature *property_signature, typeCursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, typeLength); typeCursor.setCharFormat(typeAttrs); - // BCH 1/7/2025: For dynamic properties with a name like "Effect", italicize the portion in the <> + // BCH 1/7/2025: For dynamic properties with a name like "EffectSize", italicize the portion in the <> if (Eidos_string_hasPrefix(property_signature->property_name_, "<")) { size_t end_position = property_signature->property_name_.find_first_of('>'); diff --git a/QtSLiM/QtSLiMHelpWindow.cpp b/QtSLiM/QtSLiMHelpWindow.cpp index 3fea2ad3..d3e7afac 100644 --- a/QtSLiM/QtSLiMHelpWindow.cpp +++ b/QtSLiM/QtSLiMHelpWindow.cpp @@ -855,7 +855,7 @@ void QtSLiMHelpWindow::addTopicsFromRTFFile(const QString &htmlFile, if (callName.startsWith('<')) { - // BCH 1/7/2026: This case is hit for dynamic properties, which are present in the doc with names like Effect. We don't want to + // BCH 1/7/2026: This case is hit for dynamic properties, which are present in the doc with names like EffectSize. We don't want to // check for a match, we just want to accept the property as it is. We do want to reformat it, though; we make a temporary signature for that. std::string property_name(callName.toStdString()); bool property_read_only = true; @@ -1021,7 +1021,7 @@ void QtSLiMHelpWindow::checkDocumentationOfClass(EidosClass *classObject) } } - // BCH 1/7/2026: Remove dynamic properties like "Effect" from the excess documentation list; they are not expected to have a match + // BCH 1/7/2026: Remove dynamic properties like "EffectSize" from the excess documentation list; they are not expected to have a match static const QRegularExpression nonDynamicProperties("^[^<]"); docProperties = docProperties.filter(nonDynamicProperties); diff --git a/QtSLiM/QtSLiMTablesDrawer.cpp b/QtSLiM/QtSLiMTablesDrawer.cpp index 65125786..9af3017c 100644 --- a/QtSLiM/QtSLiMTablesDrawer.cpp +++ b/QtSLiM/QtSLiMTablesDrawer.cpp @@ -87,7 +87,7 @@ static QImage imageForMutationOrInteractionType(MutationType *mut_type, Interact // so we run the sampling inside a try/catch block; if we get a raise, we just show a "?" in the plot. static bool rng_initialized = false; static Eidos_RNG_State local_rng; - EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + EffectSizeDistributionInfo &DES_info = mut_type->effect_size_distributions_[0]; // FIXME MULTITRAIT sample_size = (DES_info.DES_type_ == DESType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast draws.reserve(sample_size); @@ -100,7 +100,7 @@ static QImage imageForMutationOrInteractionType(MutationType *mut_type, Interact _Eidos_SetOneRNGSeed(local_rng, 10); // arbitrary seed, but the same seed every time - std::swap(local_rng, gEidos_RNG_SINGLE); // swap in our local RNG for DrawEffectForTrait() + std::swap(local_rng, gEidos_RNG_SINGLE); // swap in our local RNG for DrawEffectSizeForTrait() //std::clock_t start = std::clock(); @@ -108,7 +108,7 @@ static QImage imageForMutationOrInteractionType(MutationType *mut_type, Interact { for (size_t sample_count = 0; sample_count < sample_size; ++sample_count) { - double draw = mut_type->DrawEffectForTrait(0); // FIXME MULTITRAIT + double draw = mut_type->DrawEffectSizeForTrait(0); // FIXME MULTITRAIT draws.emplace_back(draw); @@ -541,7 +541,7 @@ QVariant QtSLiMMutTypeTableModel::data(const QModelIndex &p_index, int role) con std::advance(mutTypeIter, p_index.row()); slim_objectid_t mutTypeID = mutTypeIter->first; MutationType *mutationType = mutTypeIter->second; - EffectDistributionInfo &DES_info = mutationType->effect_distributions_[0]; // FIXME MULTITRAIT + EffectSizeDistributionInfo &DES_info = mutationType->effect_size_distributions_[0]; // FIXME MULTITRAIT if (p_index.column() == 0) { @@ -677,7 +677,7 @@ QVariant QtSLiMMutTypeTableModel::headerData(int section, { case 0: return QVariant("the ID for the mutation type"); case 1: return QVariant("the dominance coefficient"); - case 2: return QVariant("the distribution of effect sizes"); + case 2: return QVariant("the distribution of effect size"); case 3: return QVariant("the DES parameters"); default: return QVariant(""); } diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index 6317d940..816a5dff 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -2148,37 +2148,37 @@ bool QtSLiMWindow::checkTerminationForAutofix(QString terminationMessage) if (terminationMessage.contains("property distributionType is not defined for object element type MutationType") && (selectionString == "distributionType")) - return offerAndExecuteAutofix(selection, "effectDistributionTypeForTrait()", "The `distributionType` property of MutationType has become the method `effectDistributionTypeForTrait()`.", terminationMessage); + return offerAndExecuteAutofix(selection, "effectSizeDistributionTypeForTrait()", "The `distributionType` property of MutationType has become the method `effectSizeDistributionTypeForTrait()`.", terminationMessage); if (terminationMessage.contains("property distributionParams is not defined for object element type MutationType") && (selectionString == "distributionParams")) - return offerAndExecuteAutofix(selection, "effectDistributionParamsForTrait()", "The `distributionParams` property of MutationType has become the method `effectDistributionParamsForTrait()`.", terminationMessage); + return offerAndExecuteAutofix(selection, "effectSizeDistributionParamsForTrait()", "The `distributionParams` property of MutationType has become the method `effectSizeDistributionParamsForTrait()`.", terminationMessage); if ((afterSelection1String == "(") && terminationMessage.contains("method setDistribution() is not defined on object element type MutationType") && (selectionPlus1AfterString == "setDistribution(")) - return offerAndExecuteAutofix(selectionPlus1After, "setEffectDistributionForTrait(NULL, ", "The `setDistribution()` method of MutationType has become the method `setEffectDistributionForTrait()`.", terminationMessage); + return offerAndExecuteAutofix(selectionPlus1After, "setEffectSizeDistributionForTrait(NULL, ", "The `setDistribution()` method of MutationType has become the method `setEffectSizeDistributionForTrait()`.", terminationMessage); if (terminationMessage.contains("method drawSelectionCoefficient() is not defined on object element type MutationType") && (selectionString == "drawSelectionCoefficient")) - return offerAndExecuteAutofix(selection, "drawEffectForTrait", "The `drawSelectionCoefficient()` method of MutationType has become the method `drawEffectForTrait()`.", terminationMessage); + return offerAndExecuteAutofix(selection, "drawEffectSizeForTrait", "The `drawSelectionCoefficient()` method of MutationType has become the method `drawEffectSizeForTrait()`.", terminationMessage); if ((afterSelection1String == "(") && terminationMessage.contains("method setSelectionCoeff() is not defined on object element type Mutation") && (selectionPlus1AfterString == "setSelectionCoeff(")) - return offerAndExecuteAutofix(selectionPlus1After, "setEffectForTrait(NULL, ", "The `setSelectionCoeff()` method of Mutation has become the method `setEffectForTrait()`.", terminationMessage); + return offerAndExecuteAutofix(selectionPlus1After, "setEffectSizeForTrait(NULL, ", "The `setSelectionCoeff()` method of Mutation has become the method `setEffectSizeForTrait()`.", terminationMessage); if (terminationMessage.contains("property selectionCoeff is not defined for object element type Mutation") && (selectionString == "selectionCoeff")) - return offerAndExecuteAutofix(selection, "effect", "The `selectionCoeff` property of Mutation has become the property `effect`.", terminationMessage); + return offerAndExecuteAutofix(selection, "effectSize", "The `selectionCoeff` property of Mutation has become the property `effectSize`.", terminationMessage); if (terminationMessage.contains("property selectionCoeff is not defined for object element type Substitution") && (selectionString == "selectionCoeff")) - return offerAndExecuteAutofix(selection, "effect", "The `selectionCoeff` property of Substitution has become the property `effect`.", terminationMessage); + return offerAndExecuteAutofix(selection, "effectSize", "The `selectionCoeff` property of Substitution has become the property `effectSize`.", terminationMessage); if (terminationMessage.contains("unrecognized named argument 'selectionCoeff' to addNewMutation()") && (selectionString == "selectionCoeff")) - return offerAndExecuteAutofix(selection, "effect", "The `selectionCoeff` parameter to addNewMutation() has been renamed to `effect`.", terminationMessage); + return offerAndExecuteAutofix(selection, "effectSize", "The `selectionCoeff` parameter to addNewMutation() has been renamed to `effectSize`.", terminationMessage); return false; } diff --git a/QtSLiM/help/SLiMHelpCallbacks.html b/QtSLiM/help/SLiMHelpCallbacks.html index deabd68c..13d3c083 100644 --- a/QtSLiM/help/SLiMHelpCallbacks.html +++ b/QtSLiM/help/SLiMHelpCallbacks.html @@ -241,7 +241,7 @@

If the randomizeCallbacks parameter to initializeSLiMOptions() is T (the default), the order in which individuals are given an opportunity to reproduce with a call to reproduction() callbacks will be randomized within each subpopulation.  This partially mitigates order-dependency issues, although such issues can still arise whenever the effects of a reproduction() callback are not independent.  If randomizeCallbacks is F, individuals will be given their opportunity to reproduce in sequential order within each subpopulation, greatly increasing the risk of order-dependency problems.

As with the other callback types, multiple reproduction() callbacks may be registered and active.  In this case, all registered and active callbacks will be called for each individual, in the order that the callbacks were registered.

5.13.9  ITEM: 10. mutation() callbacks

-

SLiM auto-generates new mutations according to the current mutation rate (or rate map) and the genetic structure defined by genomic elements, their genomic element types, the mutation types those genomic element types draw from, and the distribution of effect sizes defined by those mutation types.  In nucleotide-based models, the nucleotide sequence and the mutation matrix also play a role in determining both the rate of mutation and the nucleotide mutated to.  In some models it can be desirable to modify these dynamics in some way – altering the selection coefficients of new mutations in some way, changing the mutation type used, dictating the nucleotide to be used, replacing the proposed mutation with a pre-existing mutation at the same position, or even suppressing the proposed mutation altogether.  To achieve this, one may define a mutation() callback.

+

SLiM auto-generates new mutations according to the current mutation rate (or rate map) and the genetic structure defined by genomic elements, their genomic element types, the mutation types those genomic element types draw from, and the distributions of effect size defined by those mutation types.  In nucleotide-based models, the nucleotide sequence and the mutation matrix also play a role in determining both the rate of mutation and the nucleotide mutated to.  In some models it can be desirable to modify these dynamics in some way – altering the selection coefficients of new mutations in some way, changing the mutation type used, dictating the nucleotide to be used, replacing the proposed mutation with a pre-existing mutation at the same position, or even suppressing the proposed mutation altogether.  To achieve this, one may define a mutation() callback.

A mutation() callback is defined as:

[id] [t1 [: t2]] mutation([<mut-type-id> [, <subpop-id>]]) { ... }

The mutation() callback will be called once for each new auto-generated mutation during the tick(s) in which the callback is active.  It may optionally be restricted to apply only to mutations of a particular mutation type, using the <mut-type-id> specifier; this may be a mutation type specifier such as m1, or NULL indicating no restriction.  It may also optionally be restricted to individuals generated by a specified subpopulation (usually – see below for discussion), using the <subpop-id> specifier; this should be a subpopulation specifier such as p1.  (In multispecies models, the definition must be preceded by a species specification as usual.)

diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index ed596ef2..5124088f 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -267,7 +267,7 @@

Calling this will normally affect the fitness values calculated toward the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

Note that in nonWF models that use tree-sequence recording, mutations cannot be added to an individual after the tick in which the individual is created (i.e., when the age of the individual is greater than 0), to prevent the possibility of inconsistencies in the recorded tree sequence.

+ (object<Mutation>)addNewDrawnMutation(io<MutationType> mutationType, integer position, [Nio<Subpopulation> originSubpop = NULL], [Nis nucleotide = NULL])

-

Add new mutations to the target haplosomes with the specified mutationType (specified by the MutationType object or by integer identifier), position, originTick (which may be NULL, the default, to specify the current tick; otherwise, beginning in SLiM 3.5, it must be equal to the current tick anyway, as other uses of this property have been deprecated), and originSubpop (specified by the Subpopulation object or by integer identifier, or by NULL, the default, to specify the subpopulation to which the first target haplosome belongs).  If originSubpop is supplied as an integer, it is intentionally not checked for validity; you may use arbitrary values of originSubpop to “tag” the mutations that you create.  The selection coefficients of the mutations are drawn from their mutation types; addNewMutation() may be used instead if you wish to specify selection coefficients.  All of the target haplosomes must be associated with the same Chromosome object, since each new mutation is added to all of the target haplosomes.

+

Add new mutations to the target haplosomes with the specified mutationType (specified by the MutationType object or by integer identifier), position, originTick (which may be NULL, the default, to specify the current tick; otherwise, beginning in SLiM 3.5, it must be equal to the current tick anyway, as other uses of this property have been deprecated), and originSubpop (specified by the Subpopulation object or by integer identifier, or by NULL, the default, to specify the subpopulation to which the first target haplosome belongs).  If originSubpop is supplied as an integer, it is intentionally not checked for validity; you may use arbitrary values of originSubpop to “tag” the mutations that you create (see section 26.10.1).  The effect sizes of the mutations are drawn from the distributions of effect size of their mutation types; addNewMutation() may be used instead if you wish to specify effect sizes.  All of the target haplosomes must be associated with the same Chromosome object, since each new mutation is added to all of the target haplosomes.

In non-nucleotide-based models, mutationType will always be a non-nucleotide-based mutation type, and so nucleotide must be NULL (the default).  In a nucleotide-based model, mutationType might still be non-nucleotide-based (in which case nucleotide must still be NULL), or mutationType might be nucleotide-based, in which case a non-NULL value must be supplied for nucleotide, specifying the nucleotide(s) to be associated with the new mutation(s).  Nucleotides may be specified with string values ("A", "C", "G", or "T"), or with integer values (A=0, C=1, G=2, T=3).  If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types.  No check is performed that a new mutation’s nucleotide differs from the ancestral sequence, or that its selection coefficient is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.

Beginning in SLiM 2.5 this method is vectorized, so all of these parameters may be singletons (in which case that single value is used for all mutations created by the call) or non-singleton vectors (in which case one element is used for each corresponding mutation created).  Non-singleton parameters must match in length, since their elements need to be matched up one-to-one.

The new mutations created by this method are returned, even if their actual addition is prevented by the mutation stacking policy (see the mutationStackPolicy property of MutationType).  However, the order of the mutations in the returned vector is not guaranteed to be the same as the order in which the values are specified in parameter vectors, unless the position parameter is specified in ascending order.  In other words, pre-sorting the parameters to this method into ascending order by position, using order() and subsetting, will guarantee that the order of the returned vector of mutations corresponds to the order of elements in the parameters to this method; otherwise, no such guarantee exists.

@@ -275,8 +275,8 @@

Before SLiM 4, this method also took a originGeneration parameter.  This was deprecated (the origin generation was then required to be equal to the current generation, for internal consistency), and was removed in SLiM 4.

Calling this will normally affect the fitness values calculated at the end of the current tick (but not sooner); if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

Note that in nonWF models that use tree-sequence recording, mutations cannot be added to an individual after the tick in which the individual is created (i.e., when the age of the individual is greater than 0), to prevent the possibility of inconsistencies in the recorded tree sequence.

-

+ (object<Mutation>)addNewMutation(io<MutationType> mutationType, numeric effect, integer position, [Nio<Subpopulation> originSubpop = NULL], [Nis nucleotide = NULL])

-

Add new mutations to the target haplosomes with the specified mutationType (specified by the MutationType object or by integer identifier), effect, position, originTick (which may be NULL, the default, to specify the current tick; otherwise, beginning in SLiM 3.5, it must be equal to the current tick anyway, as other uses of this property have been deprecated), and originSubpop (specified by the Subpopulation object or by integer identifier, or by NULL, the default, to specify the subpopulation to which the first target haplosome belongs).  If originSubpop is supplied as an integer, it is intentionally not checked for validity; you may use arbitrary values of originSubpop to “tag” the mutations that you create.  The addNewDrawnMutation() method may be used instead if you wish selection coefficients to be drawn from the mutation types of the mutations.  All of the target haplosomes must be associated with the same Chromosome object, since each new mutation is added to all of the target haplosomes.

+

+ (object<Mutation>)addNewMutation(io<MutationType> mutationType, numeric effectSize, integer position, [Nio<Subpopulation> originSubpop = NULL], [Nis nucleotide = NULL])

+

Add new mutations to the target haplosomes with the specified mutationType (specified by the MutationType object or by integer identifier), effectSize, position, originTick (which may be NULL, the default, to specify the current tick; otherwise, beginning in SLiM 3.5, it must be equal to the current tick anyway, as other uses of this property have been deprecated), and originSubpop (specified by the Subpopulation object or by integer identifier, or by NULL, the default, to specify the subpopulation to which the first target haplosome belongs).  If originSubpop is supplied as an integer, it is intentionally not checked for validity; you may use arbitrary values of originSubpop to “tag” the mutations that you create.  The addNewDrawnMutation() method may be used instead if you wish selection coefficients to be drawn from the mutation types of the mutations.  All of the target haplosomes must be associated with the same Chromosome object, since each new mutation is added to all of the target haplosomes.

In non-nucleotide-based models, mutationType will always be a non-nucleotide-based mutation type, and so nucleotide must be NULL (the default).  In a nucleotide-based model, mutationType might still be non-nucleotide-based (in which case nucleotide must still be NULL), or mutationType might be nucleotide-based, in which case a non-NULL value must be supplied for nucleotide, specifying the nucleotide(s) to be associated with the new mutation(s).  Nucleotides may be specified with string values ("A", "C", "G", or "T"), or with integer values (A=0, C=1, G=2, T=3).  If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types.  No check is performed that a new mutation’s nucleotide differs from the ancestral sequence, or that its selection coefficient is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.

The new mutations created by this method are returned, even if their actual addition is prevented by the mutation stacking policy (see the mutationStackPolicy property of MutationType).  However, the order of the mutations in the returned vector is not guaranteed to be the same as the order in which the values are specified in parameter vectors, unless the position parameter is specified in ascending order.  In other words, pre-sorting the parameters to this method into ascending order by position, using order() and subsetting, will guarantee that the order of the returned vector of mutations corresponds to the order of elements in the parameters to this method; otherwise, no such guarantee exists.

Beginning in SLiM 2.1, this is a class method, not an instance method.  This means that it does not get multiplexed out to all of the elements of the receiver (which would add a different new mutation to each element); instead, it is performed as a single operation, adding the same new mutation object to all of the elements of the receiver.  Before SLiM 2.1, to add the same mutation to multiple haplosomes, it was necessary to call addNewMutation() on one of the haplosomes, and then add the returned Mutation object to all of the other haplosomes using addMutations().  That is not necessary in SLiM 2.1 and later, because of this change (although doing it the old way does no harm and produces identical behavior).  Pre-2.1 code that actually relied upon the old multiplexing behavior will no longer work correctly (but this is expected to be an extremely rare pattern of usage).

@@ -300,7 +300,7 @@

In multi-chromosome models, the frequency of each mutation is assessed within the subset of target haplosomes that are associated with the same chromosome.  In other words, if a mutation is associated with chromosome 1, and the target haplosomes are associated with both chromosomes 1 and 2, the frequency of the mutation will be calculated only within the haplosomes for chromosome 1 (as you would expect).  However, you might often wish to obtain frequencies only for mutations associated with one particular chromosome.  In that case, you would probably want to pass a vector of the mutations associated with that specific chromosome, as obtained from the subsetMutations() method of Species, rather than passing NULL.  (Passing NULL in that scenario would give you frequencies of 0 for all of the mutations associated with other chromosomes in the model.)

See the +mutationCountsInHaplosomes() method to obtain integer counts instead of float frequencies.  See also the Species methods mutationCounts() and mutationFrequencies(), which might be more efficient for getting counts/frequencies for whole subpopulations or for the whole species.

 (object<Mutation>)mutationsOfType(io<MutationType>$ mutType)

-

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations in the haplosome.  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType(); if you need just the positions of matching Mutation objects, use -positionsOfMutationsOfType(); and if you are aiming for a sum of the effects of matching Mutation objects, use -sumOfMutationsOfType().  This method is provided for speed; it is much faster than the corresponding Eidos code.  See also substitutionsOfType().

+

Returns an object vector of all the mutations that are of the type specified by mutType, out of all of the mutations in the haplosome.  If you just need a count of the matching Mutation objects, rather than a vector of the matches, use -countOfMutationsOfType(); if you need just the positions of matching Mutation objects, use -positionsOfMutationsOfType(); and if you are aiming for a sum of the effect sizes of matching Mutation objects, use -sumOfMutationsOfType().  This method is provided for speed; it is much faster than the corresponding Eidos code.  See also substitutionsOfType().

– (is)nucleotides([Ni$ start = NULL], [Ni$ end = NULL], [string$ format = "string"])

Returns the nucleotide sequence for the haplosome.  This is the current ancestral sequence, as would be returned by the Chromosome method ancestralNucleotides(), with the nucleotides for any nucleotide-based mutations in the haplosome overlaid.  The range of the returned sequence may be constrained by a start position given in start and/or an end position given in end; nucleotides will be returned from start to end, inclusive.  The default value of NULL for start and end represent the first and last base positions of the chromosome, respectively.

The format of the returned sequence is controlled by the format parameter.  A format of "string" will return the sequence as a singleton string (e.g., "TATA").  A format of "char" will return a string vector with one element per nucleotide (e.g., "T", "A", "T", "A").  A format of "integer" will return an integer vector with values A=0, C=1, G=2, T=3 (e.g., 3, 0, 3, 0).  A format of "codon" will return an integer vector with values from 0 to 63, based upon successive nucleotide triplets in the sequence (which, for this format, must have a length that is a multiple of three); see the ancestralNucleotides() documentation for details.  If the sequence returned is likely to be long, the "string" format will be the most memory-efficient, and may also be the fastest (but may be harder to work with).

@@ -340,7 +340,7 @@

Removing mutations will normally affect the fitness values calculated at the end of the current tick; if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

The optional parameter substitute was added in SLiM 2.2, with a default of F for backward compatibility.  If substitute is T, Substitution objects will be created for all of the removed mutations so that they are recorded in the simulation as having fixed, just as if they had reached fixation and been removed by SLiM’s own internal machinery.  This will occur regardless of whether the mutations have in fact fixed, regardless of the convertToSubstitution property of the relevant mutation types, and regardless of whether all copies of the mutations have even been removed from the simulation (making it possible to create Substitution objects for mutations that are still segregating).  It is up to the caller to perform whatever checks are necessary to preserve the integrity of the simulation’s records.  Typically substitute will only be set to T in the context of calls like sim.subpopulations.haplosomes.removeMutations(muts, T), such that the substituted mutations are guaranteed to be entirely removed from circulation.  As mentioned above, substitute may not be T if mutations is NULL.

 (float$)sumOfMutationsOfType(io<MutationType>$ mutType, [Niso<Trait>$ trait = NULL])

-

Returns the sum of the effects of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosome, for the trait specified by trait.  The trait can be specified as the integer index or string name of a trait in the species, or directly as a Trait object; NULL can be used to represent the one defined trait in a single-trait species.

+

Returns the sum of the effect sizes of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosome, for the trait specified by trait.  The trait can be specified as the integer index or string name of a trait in the species, or directly as a Trait object; NULL can be used to represent the one defined trait in a single-trait species.

Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, sumOfMutationsOfType() provides the sum of the additive effects of the QTLs for the given mutation type.  With the support for quantitative traits that is now built into SLiM this method has largely been superseded, and is now provided mostly for backward compatibility.  This method is provided for speed; it is much faster than the corresponding Eidos code.  Note that this method also exists on Individual, for cases in which the sum across both haplosomes of an individual is desired.

5.5  Class GenomicElement

5.5.1  GenomicElement properties

@@ -533,7 +533,7 @@

AA AA 2 (full siblings)

This method does not estimate consanguinity.  For example, if one individual is itself a parent of the other individual, that is irrelevant for this method.  Similarly, in simulations of sex chromosomes, the sexes of the parents are irrelevant, even if no genetic material would have been inherited from a given parent.  See relatedness() for an assessment of pedigree-based relatedness that does estimate the consanguinity of individuals.  The sharedParentCount() method is preferable if your exact question is simply whether individuals are full siblings, half siblings, or non-siblings; in other cases, relatedness() is probably more useful.

 (float$)sumOfMutationsOfType(io<MutationType>$ mutType, [Niso<Trait>$ trait = NULL])

-

Returns the sum of the effects of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosomes of the individual, for the trait specified by trait.  The trait can be specified as the integer index or string name of a trait in the species, or directly as a Trait object; NULL can be used to represent the one defined trait in a single-trait species.

+

Returns the sum of the effect sizes of all mutations that are of the type specified by mutType, out of all of the mutations in the haplosomes of the individual, for the trait specified by trait.  The trait can be specified as the integer index or string name of a trait in the species, or directly as a Trait object; NULL can be used to represent the one defined trait in a single-trait species.

Historically, this method was often useful in models that used a particular mutation type to represent QTLs with additive effects; in that context, sumOfMutationsOfType() provides the sum of the additive effects of the QTLs for the given mutation type.  With the support for quantitative traits that is now built into SLiM this method has largely been superseded, and is now provided mostly for backward compatibility.  This method is provided for speed; it is much faster than the corresponding Eidos code.  Note that this method also exists on Haplosome, for cases in which the sum for just one haplosome is desired.

 (object<Mutation>)uniqueMutationsOfType(io<MutationType>$ mutType)

This method has been deprecated, and may be removed in a future release of SLiM.  Its functionality was replaced by mutationsFromHaplosomes() in SLiM 5.0.

@@ -696,19 +696,19 @@

5.10.1  Mutation properties

<trait-name>Dominance <–> (float$)

This dynamic property provides the dominance coefficient of the mutation for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The dominance property and the dominanceForTrait() method provide alternative ways to access dominance values.

-

<trait-name>Effect <–> (float$)

-

This dynamic property provides the effect size of the mutation for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The effect property and the effectForTrait() method provide alternative ways to access effect values.

+

<trait-name>EffectSize <–> (float$)

+

This dynamic property provides the effect size of the mutation for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The effectSize property and the effectSizeForTrait() method provide alternative ways to access effect sizes.

<trait-name>HemizygousDominance <–> (float$)

This dynamic property provides the hemizygous dominance coefficient of the mutation for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The hemizygousDominance property and the hemizygousDominanceForTrait() method provide alternative ways to access hemizygous dominance values.

chromosome => (object<Chromosome>$)

The Chromosome object with which the mutation is associated.

dominance => (float)

The dominance coefficient(s) of the mutation, taken from the default dominance coefficient(s) of its MutationType.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightDominance to access the dominance for that trait.  The dominance coefficient(s) of a mutation can be changed with the setDominanceForTrait() method.

-

If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this property does not provide that value of NAN; instead, it provides the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance value (see isIndependentDominanceForTrait() for the way to determine whether independent dominance is configured for a trait).  This realized dominance value depends upon the mutation’s corresponding effect, and may change if the effect changes.  The class Trait documentation provides further discussion of independent dominance.

+

If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this property does not provide that value of NAN; instead, it provides the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance value (see isIndependentDominanceForTrait() for the way to determine whether independent dominance is configured for a trait).  This realized dominance value depends upon the mutation’s corresponding effect size, and may change if the effect size changes.  The class Trait documentation provides further discussion of independent dominance.

Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s dominance coefficient to some number x, mut.dominance==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

-

effect => (float)

-

The effect size(s) of the mutation, drawn from the distribution of effect sizes of its MutationType.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightEffect to access the effect for that trait.  The effect size of a mutation can be changed with the setEffectForTrait() method.

-

Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s effect size to some number x, mut.effect==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

+

effectSize => (float)

+

The effect size(s) of the mutation, drawn from the distribution of effect size of its MutationType.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectSizeForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightEffectSize to access the effect size for that trait.  The effect sizes of a mutation can be changed with the setEffectSizeForTrait() method.

+

Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s effect size to some number x, mut.effectSize==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

hemizygousDominance => (float)

The hemizygous dominance coefficient(s) of the mutation, taken from the default hemizygous dominance coefficient(s) of its MutationType.  In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the hemizygousDominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightHemizygousDominance to access the hemizygous dominance for that trait.  The hemizygous dominance coefficient(s) of a mutation can be changed with the setHemizygousDominanceForTrait() method.

Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s hemizygous dominance coefficient to some number x, mut.hemizygousDominance==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

@@ -717,7 +717,7 @@

isFixed => (logical$)

T if the mutation has fixed (in the SLiM sense of having been converted to a Substitution object), F otherwise.  Since fixed/substituted mutations are removed from the simulation, you will only see this flag be T if you have held onto a mutation beyond its usual lifetime.

isNeutral => (logical$)

-

T if the mutation is neutral, F otherwise.  For a mutation to be considered neutral, it must have an effect of exactly 0.0 for all traits; its mutation type and dominance are irrelevant to this determination.

+

T if the mutation is neutral, F otherwise.  For a mutation to be considered neutral, it must have an effect size of exactly 0.0 for all traits; its mutation type and dominance are irrelevant to this determination.

isSegregating => (logical$)

T if the mutation is segregating (in the SLiM sense of not having been either lost or converted to a Substitution object), F otherwise.  Since both lost and fixed/substituted mutations are removed from the simulation, you will only see this flag be F if you have held onto a mutation beyond its usual lifetime.  Note that if isSegregating is F, isFixed will let you determine whether the mutation is no longer segregating because it was lost, or because it fixed.

mutationType => (object<MutationType>$)

@@ -738,9 +738,9 @@

5.10.2  Mutation methods

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

-

If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this method does not return that value of NAN; instead, it returns the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance value (see isIndependentDominanceForTrait() for the way to determine whether independent dominance is configured for a trait).  This realized dominance value depends upon the mutation’s corresponding effect, and may change if that effect changes.  The class Trait documentation provides further discussion of independent dominance.

-

– (float)effectForTrait([Niso<Trait> trait = NULL])

-

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this method does not return that value of NAN; instead, it returns the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance value (see isIndependentDominanceForTrait() for the way to determine whether independent dominance is configured for a trait).  This realized dominance value depends upon the mutation’s corresponding effect size, and may change if that effect size changes.  The class Trait documentation provides further discussion of independent dominance.

+

– (float)effectSizeForTrait([Niso<Trait> trait = NULL])

+

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

@@ -749,15 +749,15 @@

Sets the target mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.)  In the second pattern, dominance is a singleton value; this sets the given dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the dominance for each of the specified traits in one mutation before moving to the next mutation.

A dominance value of NAN for a given trait configures the mutation to use independent dominance for that trait; see the class Trait documentation for discussion of this feature, which can also be set at the MutationType level with initializeMutationType() or setDefaultDominanceForTrait().  A given mutation may be configured to exhibit independent dominance for some traits and not for others; a mixed configuration is allowed.

-

+ (void)setEffectForTrait([Niso<Trait> trait = NULL], [Nif effect = NULL])

-

Sets the mutation’s effect(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

-

The parameter effect must follow one of four patterns.  In the first pattern, effect is NULL; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation.  (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.)  In the second pattern, effect is a singleton value; this sets the given effect for each of the specified traits in each target mutation.  In the third pattern, effect is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation.  In the fourth pattern, effect is of length equal to the number of specified traits times the number of target mutations; this uses effect to provide a different effect value for each trait in each mutation, using consecutive values from effect to set the effect for each of the specified traits in one mutation before moving to the next mutation.

+

+ (void)setEffectSizeForTrait([Niso<Trait> trait = NULL], [Nif effectSize = NULL])

+

Sets the mutation’s effect size(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

The parameter effectSize must follow one of four patterns.  In the first pattern, effectSize is NULL; this draws the effect size for each of the specified traits from the corresponding distribution of effect size for the mutation type of the mutation in each target mutation.  (Note that mutation effect sizes are automatically drawn from these distributions when a mutation is created; this re-draws new effect sizes.)  In the second pattern, effectSize is a singleton value; this sets the given effect size for each of the specified traits in each target mutation.  In the third pattern, effectSize is of length equal to the number of specified traits; this sets the effect size for each of the specified traits to the corresponding effect size in each target mutation.  In the fourth pattern, effectSize is of length equal to the number of specified traits times the number of target mutations; this uses effectSize to provide a different effect size for each trait in each mutation, using consecutive values from effectSize to set the effect size for each of the specified traits in one mutation before moving to the next mutation.

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  This policy applies to mutational effects, so the final effect of each mutation (perhaps influenced by a dominance coefficient) will be clamped prior to use in phenotype calculations.

+ (void)setHemizygousDominanceForTrait([Niso<Trait> trait = NULL], [Nif dominance = NULL])

Sets the mutation’s hemizygous dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default hemizygous dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation hemizygous dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default hemizygous dominance values.)  In the second pattern, dominance is a singleton value; this sets the given hemizygous dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the hemizygous dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different hemizygous dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the hemizygous dominance for each of the specified traits in one mutation before moving to the next mutation.

– (void)setMutationType(io<MutationType>$ mutType)

-

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  The effects and dominance coefficients of existing mutations are not changed, since those are properties of the mutation objects themselves; they can be changed explicitly using the setEffectForTrait() and setDominanceForTrait() methods of Mutation if so desired.

+

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  The effect sizes and dominance coefficients of existing mutations are not changed, since those are properties of the mutation objects themselves; they can be changed explicitly using the setEffectSizeForTrait() and setDominanceForTrait() methods of Mutation if so desired.

In nucleotide-based models, a restriction applies: nucleotide-based mutations may not be changed to a non-nucleotide-based mutation type, and non-nucleotide-based mutations may not be changed to a nucleotide-based mutation type.

5.11  Class MutationType

5.11.1  MutationType properties

@@ -789,36 +789,36 @@

5.11.2  MutationType methods

– (float)defaultDominanceForTrait([Niso<Trait> trait = NULL])

Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their dominance property, but that can be changed later with the Mutation method setDominanceForTrait().

-

If a default dominance coefficient of NAN has been set for a given trait, to configure a default of “independent dominance” for that trait, NAN will be returned by this method for that trait; a realized dominance value cannot be returned since the corresponding effect size is not necessarily known.  See the Mutation method dominanceForTrait() regarding obtaining the realized dominance coefficient used in a particular mutation, given that mutation’s effect for the trait.

+

If a default dominance coefficient of NAN has been set for a given trait, to configure a default of “independent dominance” for that trait, NAN will be returned by this method for that trait; a realized dominance value cannot be returned since the corresponding effect size is not necessarily known.  See the Mutation method dominanceForTrait() regarding obtaining the realized dominance coefficient used in a particular mutation, given that mutation’s effect size for the trait.

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

– (float)defaultHemizygousDominanceForTrait([Niso<Trait> trait = NULL])

Returns the default hemizygous dominance coefficient used for the specified trait or traits, for mutations of this type when hemizygous (i.e., when present in only one copy because they are facing a null haplosome, such as for an X chromosome in a male).  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default hemizygous dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their hemizygousDominance property, but that can be changed later with the Mutation method setHemizygousDominanceForTrait().

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

-

– (float)drawEffectForTrait([Niso<Trait> trait = NULL], [integer$ n = 1])

-

Draws and returns a vector of n mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  See the MutationType class documentation for discussion of the supported distributions and their uses.  If the distribution of effects is of type "s", this method will result in synchronous execution of the script associated with the distribution of effects.

-

– (fs)effectDistributionParamsForTrait([Niso<Trait> trait = NULL])

-

Returns the parameters that configure the distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DES type "s", and type float for all other DES types.

-

– (string)effectDistributionTypeForTrait([Niso<Trait> trait = NULL])

-

Returns the type of distribution of effects for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

-

– (io<DataFrame>$)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

+

– (float)drawEffectSizeForTrait([Niso<Trait> trait = NULL], [integer$ n = 1])

+

Draws and returns a vector of n mutation effect sizes using the distribution of effect size for the specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  See the MutationType class documentation for discussion of the supported distributions and their uses.  If the distribution of effect size is of type "s", this method will result in synchronous execution of the script associated with the distribution of effect size.

+

– (fs)effectSizeDistributionParamsForTrait([Niso<Trait> trait = NULL])

+

Returns the parameters that configure the distribution of effect size for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution parameters will be of type string for DES type "s", and type float for all other DES types.

+

– (string)effectSizeDistributionTypeForTrait([Niso<Trait> trait = NULL])

+

Returns the type of the distribution of effect size for the specified trait or traits.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The distribution type will be one of "f", "g", "e", "n", "p", "w", or "s" , as discussed in the MutationType class documentation.

+

– (io<DataFrame>$)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effectSize = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

Returns mutation data produced by the mutation type’s logging facility, as configured by logMutationData().  The data returned can be in the form of means across all logged mutations (for kind="mean"), standard deviations across all logged mutations (for kind="sd"), or separate values for each mutation (for kind="values"); kind="count" is also allowed, and simply returns a singleton integer providing the number of entries (i.e., the number of mutations) that have been recorded.  If logging only of means was enabled (with the meanOnly=T option to logMutationData()), only kind="mean" and kind="count" are allowed, since separate values for each mutation are then not logged.

-

The remaining flags control which columns of data should be returned (for kind options other than "count"); see logMutationData() for a summary of the mutation properties they refer to.  A DataFrame object will be returned with named columns of values (or means, or standard deviations) for each specified data column.  The trait property specifies which the traits for which values should be returned, with respect to the effect, dominance, and hemizygousDominance flags; see logMutationData() for further description.  As a convenience, if all of these flags are F (the default), all of the logged data columns will be returned (rather than returning no data at all).  Flags set to T for data columns that were not actually logged will simply be ignored; similarly, traits specified by trait that were not actually logged will simply be ignored.  See the Eidos manual for the DataFrame class documentation.

-

– (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effect = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

+

The remaining flags control which columns of data should be returned (for kind options other than "count"); see logMutationData() for a summary of the mutation properties they refer to.  A DataFrame object will be returned with named columns of values (or means, or standard deviations) for each specified data column.  The trait property specifies which the traits for which values should be returned, with respect to the effectSize, dominance, and hemizygousDominance flags; see logMutationData() for further description.  As a convenience, if all of these flags are F (the default), all of the logged data columns will be returned (rather than returning no data at all).  Flags set to T for data columns that were not actually logged will simply be ignored; similarly, traits specified by trait that were not actually logged will simply be ignored.  See the Eidos manual for the DataFrame class documentation.

+

– (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], [Niso<Trait> trait = NULL], [logical$ effectSize = F], [logical$ dominance = F], [logical$ hemizygousDominance = F])

Starts or ends logging of data about new mutations belonging to the target mutation type.  If autogeneratedOnly is T (the default), only new mutations generated automatically by SLiM will be logged (including mutations that are substituted in for an auto-generated mutation using a mutation() callback; that is still considered part of the auto-generation process).  If autogeneratedOnly is F, mutations generated in script, such as with addNewMutation(), addNewDrawnMutation(), and reading from files such as VCF, MS, or .trees, will also be logged.  The logged information can be obtained later with the loggedData() method.  Once logging has been started with enable=T it cannot be modified, only stopped with enable=F; and if logging is subsequently resumed with enable=T, any previously logged data will be discarded.  (This can be useful if you wish to limit the size of the in-memory data while continuing to log new data: periodically write the accumulated data to a file and then disable and re-enable logging to discard the old data.)

If meanOnly is F (the default), values for each new mutation will be kept separately.  Beware: the memory usage entailed by this option can be extremely large!  Alternatively, if meanOnly is T, only a running sum, used to compute a mean, will be kept for each type of data; the memory usage for this option will be small and constant, but of course a mean is more useful for some columns of data than others.  If per-mutation data is desired for any one column, use meanOnly=F; this option cannot be controlled independently for the various columns of data being logged.

Next are parameters that control which mutation properties will be logged: id controls the id property; mutationTypeID controls the id property of the mutation’s mutation type (this will be the same for all mutations logged by a given MutationType, it can be useful if you combine datasets from more than one mutation type later); chromosomeID controls the id property of the mutation’s associated chromosome; position controls the position property; nucleotideValue controls the nucleotideValue property; originTick controls the originTick property; subpopID controls the subpopID property; and tag controls the tag property.  Data columns will be added in the order of these parameters; the id column will be first, if requested, for example.

-

Last come parameters that control the logging of trait-associated data for the new mutations.  The trait parameter controls which traits will be logged.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  For each specified trait, the effect parameter controls logging of the effect size, the dominance parameter controls the dominance, and the hemizygousDominance parameter controls the hemizygous dominance.  Data columns for this trait-associated data will be grouped by trait; for example, if two traits named height and weight are specified, and the effect=T and dominance=T flags are specified, then columns heightEffect, heightDominance, weightEffect, and weightDominance will be added, in that order.

+

Last come parameters that control the logging of trait-associated data for the new mutations.  The trait parameter controls which traits will be logged.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  For each specified trait, the effectSize parameter controls logging of the effect size, the dominance parameter controls the dominance, and the hemizygousDominance parameter controls the hemizygous dominance.  Data columns for this trait-associated data will be grouped by trait; for example, if two traits named height and weight are specified, and the effectSize=T and dominance=T flags are specified, then columns heightEffect, heightDominance, weightEffect, and weightDominance will be added, in that order.

Note that logging occurs after all mutation() callbacks have been called, at the point when the new mutation is actually added to the simulation.  If addition of a new mutation is prevented, by a mutation() callback or by the current stacking policy, that mutation will not be logged.  The information logged will be the mutation’s properties at the moment that it is added; a tag value set by a mutation() callback will therefore be captured in the logged data, for example.  Changes to mutations made after that point will not be preserved in the log; the log is a snapshot of the moment of each mutation’s addition to the simulation.

– (void)setDefaultDominanceForTrait(Niso<Trait> trait, float dominance)

Set the default dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of defaultDominance is used for each corresponding trait).

As for initializeMutationType(), a dominance value of NAN for a given trait configures the mutation type to use “independent dominance” for that trait in new mutations of that type; see the class Trait documentation for discussion of independent dominance.  A mutation type may be configured to use independent dominance for some traits and not for others; a mixed configuration is allowed.

– (void)setDefaultHemizygousDominanceForTrait(Niso<Trait> trait, float dominance)

Set the default hemizygous dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default hemizygous dominance for all specified traits), or must match the number of specified traits (in which case one element of dominance is used for each corresponding trait).

-

– (void)setEffectDistributionForTrait(Niso<Trait> trait, string$ distributionType, ...)

-

Set the distribution of effects for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.

-

The distributionType may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for the exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  See the MutationType class documentation for discussion of these distributions and their uses.  The distribution of effects for a mutation type is normally a constant in simulations, so be sure you know what you are doing.

+

– (void)setEffectSizeDistributionForTrait(Niso<Trait> trait, string$ distributionType, ...)

+

Set the distribution of effect size for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.

+

The distributionType may be "f", in which case the ellipsis ... should supply a numeric$ fixed effect size; "e", in which case the ellipsis should supply a numeric$ mean effect size for the exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean effect size and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean effect size and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean effect size and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  See the MutationType class documentation for discussion of these distributions and their uses.  The distribution of effect size for a mutation type is normally a constant in simulations, so be sure you know what you are doing.

5.12  Class Plot

5.12.1  Plot properties

title => (string$)

@@ -1398,16 +1398,16 @@

5.18.1  Substitution properties

<trait-name>Dominance => (float$)

This dynamic property provides the dominance coefficient of the substitution for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The dominance property and the dominanceForTrait() method provide alternative ways to access dominance values.

-

<trait-name>Effect => (float$)

-

This dynamic property provides the effect size of the substitution for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The effect property and the effectForTrait() method provide alternative ways to access effect values.

+

<trait-name>EffectSize => (float$)

+

This dynamic property provides the effect size of the substitution for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The effectSize property and the effectSizeForTrait() method provide alternative ways to access effect sizes.

<trait-name>HemizygousDominance => (float$)

This dynamic property provides the hemizygous dominance coefficient of the substitution for the trait named <trait-name>.  A dynamic property with this naming pattern will be defined for each trait in each species.  The hemizygousDominance property and the hemizygousDominanceForTrait() method provide alternative ways to access hemizygous dominance values.

chromosome => (object<Chromosome>$)

The Chromosome object with which the substitution is associated.

dominance => (float)

The dominance coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightDominance to access the dominance for that trait.

-

effect => (float)

-

The selection coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightEffect to access the effect for that trait.

+

effectSize => (float)

+

The effect size(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectSizeForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightEffectSize to access the effect size for that trait.

hemizygousDominance => (float)

The hemizygous dominance coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the hemizygousDominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightHemizygousDominance to access the dominance for that trait.

id => (integer$)

@@ -1433,8 +1433,8 @@

5.18.2  Substitution methods

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

Returns the substitution’s dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

-

– (float)effectForTrait([Niso<Trait> trait = NULL])

-

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effects for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

– (float)effectSizeForTrait([Niso<Trait> trait = NULL])

+

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

Returns the substitution’s hemizygous dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 3de050cd..813f4998 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -106,7 +106,7 @@

(object<MutationType>$)initializeMutationType(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

Add a mutation type at initialization time.  The id must not already be used for any mutation type in the simulation.  The id parameter may be either an integer giving the ID of the new mutation type, or a string giving the name of the new mutation type (such as "m5" to specify an ID of 5).  The global symbol for the new mutation type, such as m5, is immediately available; the return value also provides the new object.

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  A dominanceCoeff value of NAN configures the mutation type to use “independent dominance” for new mutations of that type, for all traits; see the class Trait documentation for discussion of independent dominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but the setDefaultHemizygousDominanceForTrait() method can configure it subsequently if desired.

-

The distributionType and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits.  The DES for the mutation type for a specific trait can subsequently be separately configured with the setEffectDistributionForTrait() method if desired.  The distributionType parameter may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for an exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  If distributionType is NULL (the default), a fixed effect of 0.0 is used, representing a neutral DES; this is equivalent to type "f" except that the value 0.0 is assumed and must not be supplied.  See the MutationType class documentation for discussion of the various DESs and their uses.

+

The distributionType and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits.  The DES for the mutation type for a specific trait can subsequently be separately configured with the setEffectSizeDistributionForTrait() method if desired.  The distributionType parameter may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for an exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  If distributionType is NULL (the default), a fixed effect size of 0.0 is used, representing a neutral DES; this is equivalent to type "f" except that the value 0.0 is assumed and must not be supplied.  See the MutationType class documentation for discussion of the various DESs and their uses.

Note that by default in WF models, all mutations of a given mutation type will be converted into Substitution objects when they reach fixation, for efficiency reasons.  If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the convertToSubstitution property of MutationType to F.  In contrast, by default in nonWF models mutations will not be converted into Substitution objects when they reach fixation; convertToSubstitution is F by default in nonWF models.  To enable conversion in nonWF models for neutral mutation types with no indirect fitness effects, you should therefore set convertToSubstitution to T.

(object<MutationType>$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

Add a nucleotide-based mutation type at initialization time.  This function is identical to initializeMutationType() except that the new mutation type will be nucleotide-based – in other words, mutations belonging to the new mutation type will have an associated nucleotide.  This function may be called only in nucleotide-based models (as enabled by the nucleotideBased parameter to initializeSLiMOptions()).

@@ -274,7 +274,7 @@

The implementation of calcTajimasD(), viewable with functionSource(), treats every mutation as independent in the heterozygosity calculations.  One could regard this choice as embodying an infinite-sites interpretation of the segregating mutations, as with calcHeterozygosity().  Indeed, Tajima’s D can be modified with finite-sites models of π and θ (Misawa and Tajima 1997) though these are not used here.  In most biologically realistic models, such genetic states will be quite rare, and so the impact of this assumption will be negligible; however, in some models this distinction may be important.  See calcPairHeterozygosity() for further discussion.  This function was written by Nick Bailey (currently affiliated with CNRS and the Laboratory of Biometry and Evolutionary Biology at University Lyon 1), with helpful input from Peter Ralph.

(float$)calcVA(object<Individual> individuals, io<MutationType>$ mutType)

Calculates VA, the additive genetic variance, among a vector of individuals (containing at least two elements) passed in individuals, in a particular mutation type mutType that represents quantitative trait loci (QTLs) influencing a quantitative phenotypic trait.  The mutType parameter may be either an integer representing the ID of the desired mutation type, or a MutationType object specified directly.

-

This function assumes that mutations of type mutType encode their effect size upon the quantitative trait in their effect property, as is fairly standard in SLiM.  The implementation of calcVA(), which is viewable with functionSource(), is quite simple; if effect sizes are stored elsewhere (such as with setValue()), a new user-defined function following the pattern of calcVA() can easily be written.

+

This function assumes that mutations of type mutType encode their effect size upon the quantitative trait in their effect property, as is standard in SLiM.  The implementation of calcVA(), which is viewable with functionSource(), is quite simple; if effect sizes are stored elsewhere (such as with setValue()), a new user-defined function following the pattern of calcVA() can easily be written.

(float$)calcWattersonsTheta(object<Haplosome> haplosomes, [No<Mutation> muts = NULL], [Ni$ start = NULL], [Ni$ end = NULL])

Calculates Watterson’s theta (a metric of genetic diversity comparable to heterozygosity) for a vector of haplosomes (containing at least one element), based upon the mutations in the haplosomes.  Often haplosomes will be all of the haplosomes in a subpopulation, or in the entire population, but any haplosome vector may be used.  By default, with muts=NULL, the calculation is based upon all mutations in the simulation; the calculation can instead be based upon a subset of mutations, such as mutations of a specific mutation type, by passing the desired vector of mutations for muts.

The calculation can be narrowed to apply to only a window – a subrange of the full chromosome – by passing the interval bounds [start, end] for the desired window.  In this case, the vector of mutations used for the calculation will be subset to include only mutations within the specified window.  The default behavior, with start and end of NULL, provides the haplosome-wide Watterson’s theta.

diff --git a/SLiMgui/ChromosomeView.mm b/SLiMgui/ChromosomeView.mm index ba3e8d46..8b29a1a6 100644 --- a/SLiMgui/ChromosomeView.mm +++ b/SLiMgui/ChromosomeView.mm @@ -709,7 +709,7 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome if (mut_type->mutation_type_displayed_) { bool mut_type_fixed_color = !mut_type->color_.empty(); - EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + EffectSizeDistributionInfo &DES_info = mut_type->effect_size_distributions_[0]; // FIXME MULTITRAIT // We optimize fixed-DES mutation types only, and those using a fixed color set by the user if ((DES_info.DES_type_ == DESType::kFixed) || mut_type_fixed_color) diff --git a/SLiMgui/CocoaExtra.mm b/SLiMgui/CocoaExtra.mm index 3af78fb3..62a183d3 100644 --- a/SLiMgui/CocoaExtra.mm +++ b/SLiMgui/CocoaExtra.mm @@ -555,7 +555,7 @@ - (void)drawRect:(NSRect)dirtyRect // so we run the sampling inside a try/catch block; if we get a raise, we just show a "?" in the plot. static bool rng_initialized = false; static Eidos_RNG_State local_rng; - EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + EffectSizeDistributionInfo &DES_info = mut_type->effect_size_distributions_[0]; // FIXME MULTITRAIT sample_size = (DES_info.DES_type_ == DESType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast draws.reserve(sample_size); @@ -570,7 +570,7 @@ - (void)drawRect:(NSRect)dirtyRect Eidos_RNG_State *slim_rng_state = EIDOS_STATE_RNG(omp_get_thread_num()); - std::swap(local_rng, *slim_rng_state); // swap in our local RNG for DrawEffectForTrait() + std::swap(local_rng, *slim_rng_state); // swap in our local RNG for DrawEffectSizeForTrait() //std::clock_t start = std::clock(); @@ -578,7 +578,7 @@ - (void)drawRect:(NSRect)dirtyRect { for (int sample_count = 0; sample_count < sample_size; ++sample_count) { - double draw = mut_type->DrawEffectForTrait(0); // FIXME MULTITRAIT + double draw = mut_type->DrawEffectSizeForTrait(0); // FIXME MULTITRAIT draws.emplace_back(draw); diff --git a/SLiMgui/SLiMHelpCallbacks.rtf b/SLiMgui/SLiMHelpCallbacks.rtf index e8350897..40793dd7 100644 --- a/SLiMgui/SLiMHelpCallbacks.rtf +++ b/SLiMgui/SLiMHelpCallbacks.rtf @@ -1307,7 +1307,7 @@ As with the other callback types, multiple \f0\fs22 callbacks\ \pard\pardeftab397\fi274\ri720\sb40\sa40\partightenfactor0 -\f2\i0 \cf2 SLiM auto-generates new mutations according to the current mutation rate (or rate map) and the genetic structure defined by genomic elements, their genomic element types, the mutation types those genomic element types draw from, and the distribution of effect sizes defined by those mutation types. In nucleotide-based models, the nucleotide sequence and the mutation matrix also play a role in determining both the rate of mutation and the nucleotide mutated to. In some models it can be desirable to modify these dynamics in some way \'96 altering the selection coefficients of new mutations in some way, changing the mutation type used, dictating the nucleotide to be used, replacing the proposed mutation with a pre-existing mutation at the same position, or even suppressing the proposed mutation altogether. To achieve this, one may define a +\f2\i0 \cf2 SLiM auto-generates new mutations according to the current mutation rate (or rate map) and the genetic structure defined by genomic elements, their genomic element types, the mutation types those genomic element types draw from, and the distributions of effect size defined by those mutation types. In nucleotide-based models, the nucleotide sequence and the mutation matrix also play a role in determining both the rate of mutation and the nucleotide mutated to. In some models it can be desirable to modify these dynamics in some way \'96 altering the selection coefficients of new mutations in some way, changing the mutation type used, dictating the nucleotide to be used, replacing the proposed mutation with a pre-existing mutation at the same position, or even suppressing the proposed mutation altogether. To achieve this, one may define a \f3\fs18 mutation() \f2\fs22 callback.\ A diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 8a6f3910..adb5b1cd 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -1733,9 +1733,9 @@ Note that in nonWF models that use tree-sequence recording, mutations cannot be \f3\fs18 integer \f4\fs20 , it is intentionally not checked for validity; you may use arbitrary values of \f3\fs18 originSubpop -\f4\fs20 to \'93tag\'94 the mutations that you create. The selection coefficients of the mutations are drawn from their mutation types; +\f4\fs20 to \'93tag\'94 the mutations that you create (see section 26.10.1). The effect sizes of the mutations are drawn from the distributions of effect size of their mutation types; \f3\fs18 addNewMutation() -\f4\fs20 may be used instead if you wish to specify selection coefficients. All of the target haplosomes must be associated with the same +\f4\fs20 may be used instead if you wish to specify effect sizes. All of the target haplosomes must be associated with the same \f3\fs18 Chromosome \f4\fs20 object, since each new mutation is added to all of the target haplosomes.\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -1811,7 +1811,7 @@ Beginning in SLiM 2.1, this is a class method, not an instance method. This mea \f4\fs20 ), to prevent the possibility of inconsistencies in the recorded tree sequence.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 +\'a0(object)addNewMutation(io\'a0mutationType, numeric\'a0effect, integer\'a0position, [Nio\'a0originSubpop\'a0=\'a0NULL]\cf2 \expnd0\expndtw0\kerning0 +\f3\fs18 \cf0 +\'a0(object)addNewMutation(io\'a0mutationType, numeric\'a0effectSize, integer\'a0position, [Nio\'a0originSubpop\'a0=\'a0NULL]\cf2 \expnd0\expndtw0\kerning0 , [Nis\'a0nucleotide\'a0=\'a0NULL]\cf0 \kerning1\expnd0\expndtw0 ) \f5 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -1823,7 +1823,7 @@ Beginning in SLiM 2.1, this is a class method, not an instance method. This mea \f4\fs20 object or by \f3\fs18 integer \f4\fs20 identifier), -\f3\fs18 effect +\f3\fs18 effectSize \f4\fs20 , \f3\fs18 position \f4\fs20 , @@ -2137,7 +2137,7 @@ See the \f3\fs18 Mutation \f4\fs20 objects, use \f3\fs18 -positionsOfMutationsOfType() -\f4\fs20 ; and if you are aiming for a sum of the effects of matching +\f4\fs20 ; and if you are aiming for a sum of the effect sizes of matching \f3\fs18 Mutation \f4\fs20 objects, use \f3\fs18 -sumOfMutationsOfType() @@ -2719,7 +2719,7 @@ The optional parameter \f5 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 Returns the sum of the effects of all mutations that are of the type specified by +\f4\fs20 \cf2 Returns the sum of the effect sizes of all mutations that are of the type specified by \f3\fs18 mutType \f4\fs20 , out of all of the mutations in the haplosome, for the trait specified by \f3\fs18 trait @@ -4557,7 +4557,7 @@ More specifically, this method uses the parental pedigree IDs from the pedigree \f5 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 Returns the sum of the effects of all mutations that are of the type specified by +\f4\fs20 \cf2 Returns the sum of the effect sizes of all mutations that are of the type specified by \f3\fs18 mutType \f4\fs20 , out of all of the mutations in the haplosomes of the individual, for the trait specified by \f3\fs18 trait @@ -6205,17 +6205,17 @@ You can get the \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f2\i\fs18 \cf2 -\f3\i0 Effect \expnd0\expndtw0\kerning0 +\f3\i0 EffectSize \expnd0\expndtw0\kerning0 <\'96>\kerning1\expnd0\expndtw0 (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 This dynamic property provides the effect size of the mutation for the trait named \f2\i\fs18 \f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The -\f3\fs18 effect +\f3\fs18 effectSize \f4\fs20 property and the -\f3\fs18 effectForTrait() -\f4\fs20 method provide alternative ways to access effect values.\ +\f3\fs18 effectSizeForTrait() +\f4\fs20 method provide alternative ways to access effect sizes.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f2\i\fs18 \cf2 @@ -6266,7 +6266,7 @@ If the target mutation has been configured to exhibit independent dominance for \f3\fs18 NAN \f4\fs20 ; instead, it provides the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the \'93realized\'94 dominance value (see \f3\fs18 isIndependentDominanceForTrait() -\f4\fs20 for the way to determine whether independent dominance is configured for a trait). This realized dominance value depends upon the mutation\'92s corresponding effect, and may change if the effect changes. The class +\f4\fs20 for the way to determine whether independent dominance is configured for a trait). This realized dominance value depends upon the mutation\'92s corresponding effect size, and may change if the effect size changes. The class \f3\fs18 Trait \f4\fs20 documentation provides further discussion of independent dominance.\ Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation @@ -6280,28 +6280,28 @@ Note that dominance coefficients in SLiM have a quirk: they are stored internall \f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 effect => (float)\ +\f3\fs18 \cf2 effectSize => (float)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The effect size(s) of the mutation, drawn from the distribution of effect sizes of its +\f4\fs20 \cf2 The effect size(s) of the mutation, drawn from the distribution of effect size of its \f3\fs18 MutationType \f4\fs20 . In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the -\f3\fs18 effectForTrait() +\f3\fs18 effectSizeForTrait() \f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named \f3\fs18 height \f4\fs20 , for example, then \f3\fs18 Mutation \f4\fs20 objects will have a dynamic property named -\f3\fs18 heightEffect -\f4\fs20 to access the effect for that trait. The effect size of a mutation can be changed with the -\f3\fs18 setEffectForTrait() +\f3\fs18 heightEffectSize +\f4\fs20 to access the effect size for that trait. The effect sizes of a mutation can be changed with the +\f3\fs18 setEffectSizeForTrait() \f4\fs20 method.\ Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s effect size to some number \f3\fs18 x \f4\fs20 , -\f3\fs18 mut.effect==x +\f3\fs18 mut.effectSize==x \f4\fs20 may be \f3\fs18 F \f4\fs20 due to floating-point rounding error. Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.\ @@ -6358,7 +6358,7 @@ Note that dominance coefficients in SLiM have a quirk: they are stored internall \cf2 T \f4\fs20 if the mutation is neutral, \f3\fs18 F -\f4\fs20 otherwise. For a mutation to be considered neutral, it must have an effect of exactly +\f4\fs20 otherwise. For a mutation to be considered neutral, it must have an effect size of exactly \f3\fs18 0.0 \f4\fs20 for all traits; its mutation type and dominance are irrelevant to this determination.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -6500,12 +6500,12 @@ If the target mutation has been configured to exhibit independent dominance for \f3\fs18 NAN \f4\fs20 ; instead, it returns the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the \'93realized\'94 dominance value (see \f3\fs18 isIndependentDominanceForTrait() -\f4\fs20 for the way to determine whether independent dominance is configured for a trait). This realized dominance value depends upon the mutation\'92s corresponding effect, and may change if that effect changes. The class +\f4\fs20 for the way to determine whether independent dominance is configured for a trait). This realized dominance value depends upon the mutation\'92s corresponding effect size, and may change if that effect size changes. The class \f3\fs18 Trait \f4\fs20 documentation provides further discussion of independent dominance.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Niso\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)effectSizeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the mutation\'92s effect size for the trait(s) specified by @@ -6522,7 +6522,7 @@ If the target mutation has been configured to exhibit independent dominance for \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL -\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effects for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effect sizes for a given target mutation will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -6618,10 +6618,10 @@ A dominance value of \f4\fs20 . A given mutation may be configured to exhibit independent dominance for some traits and not for others; a mixed configuration is allowed.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 +\'a0(void)setEffectForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0effect\'a0=\'a0NULL])\ +\f3\fs18 \cf2 +\'a0(void)setEffectSizeForTrait([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0effectSize\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 Sets the mutation\'92s effect(s) for the trait(s) specified by +\f4\fs20 \cf2 Sets the mutation\'92s effect size(s) for the trait(s) specified by \f3\fs18 trait \f4\fs20 . The traits can be specified as \f3\fs18 integer @@ -6633,22 +6633,22 @@ A dominance value of \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ The parameter -\f3\fs18 effect +\f3\fs18 effectSize \f4\fs20 must follow one of four patterns. In the first pattern, -\f3\fs18 effect +\f3\fs18 effectSize \f4\fs20 is \f3\fs18 NULL -\f4\fs20 ; this draws the effect for each of the specified traits from the corresponding distribution of effect sizes from the mutation type of the mutation in each target mutation. (Note that mutation offsets are automatically drawn from these distributions when a mutation is created; this re-draws new effect values.) In the second pattern, -\f3\fs18 effect -\f4\fs20 is a singleton value; this sets the given effect for each of the specified traits in each target mutation. In the third pattern, -\f3\fs18 effect -\f4\fs20 is of length equal to the number of specified traits; this sets the effect for each of the specified traits to the corresponding effect value in each target mutation. In the fourth pattern, -\f3\fs18 effect +\f4\fs20 ; this draws the effect size for each of the specified traits from the corresponding distribution of effect size for the mutation type of the mutation in each target mutation. (Note that mutation effect sizes are automatically drawn from these distributions when a mutation is created; this re-draws new effect sizes.) In the second pattern, +\f3\fs18 effectSize +\f4\fs20 is a singleton value; this sets the given effect size for each of the specified traits in each target mutation. In the third pattern, +\f3\fs18 effectSize +\f4\fs20 is of length equal to the number of specified traits; this sets the effect size for each of the specified traits to the corresponding effect size in each target mutation. In the fourth pattern, +\f3\fs18 effectSize \f4\fs20 is of length equal to the number of specified traits times the number of target mutations; this uses -\f3\fs18 effect -\f4\fs20 to provide a different effect value for each trait in each mutation, using consecutive values from -\f3\fs18 effect -\f4\fs20 to set the effect for each of the specified traits in one mutation before moving to the next mutation.\ +\f3\fs18 effectSize +\f4\fs20 to provide a different effect size for each trait in each mutation, using consecutive values from +\f3\fs18 effectSize +\f4\fs20 to set the effect size for each of the specified traits in one mutation before moving to the next mutation.\ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 0.0 \f4\fs20 as documented in the @@ -6698,8 +6698,8 @@ The parameter \f3\fs18 integer \f4\fs20 identifier or a \f3\fs18 MutationType -\f4\fs20 object). The effects and dominance coefficients of existing mutations are not changed, since those are properties of the mutation objects themselves; they can be changed explicitly using the -\f3\fs18 setEffectForTrait() +\f4\fs20 object). The effect sizes and dominance coefficients of existing mutations are not changed, since those are properties of the mutation objects themselves; they can be changed explicitly using the +\f3\fs18 setEffectSizeForTrait() \f4\fs20 and \f3\fs18 setDominanceForTrait() \f4\fs20 methods of @@ -6939,7 +6939,7 @@ If a default dominance coefficient of \f3\fs18 Mutation \f4\fs20 method \f3\fs18 dominanceForTrait() -\f4\fs20 regarding obtaining the realized dominance coefficient used in a particular mutation, given that mutation\'92s effect for the trait.\ +\f4\fs20 regarding obtaining the realized dominance coefficient used in a particular mutation, given that mutation\'92s effect size for the trait.\ Note that dominance coefficients are not bounded. A dominance coefficient greater than \f3\fs18 1.0 \f4\fs20 may be used to achieve an overdominance effect. By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ @@ -6978,12 +6978,12 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 type normally used by Eidos. This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)drawEffectForTrait([Niso\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ +\f3\fs18 \cf2 \'96\'a0(float)drawEffectSizeForTrait([Niso\'a0trait\'a0=\'a0NULL], [integer$\'a0n\'a0=\'a01])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Draws and returns a vector of \f3\fs18 n -\f4\fs20 mutation effects using the distribution of effects for the specified trait or traits, for the target mutation type. The traits can be specified as +\f4\fs20 mutation effect sizes using the distribution of effect size for the specified trait or traits, for the target mutation type. The traits can be specified as \f3\fs18 integer \f4\fs20 indices or \f3\fs18 string @@ -6993,15 +6993,15 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f3\fs18 NULL \f4\fs20 represents all of the traits in the species. See the \f3\fs18 MutationType -\f4\fs20 class documentation for discussion of the supported distributions and their uses. If the distribution of effects is of type +\f4\fs20 class documentation for discussion of the supported distributions and their uses. If the distribution of effect size is of type \f3\fs18 "s" -\f4\fs20 , this method will result in synchronous execution of the script associated with the distribution of effects.\ +\f4\fs20 , this method will result in synchronous execution of the script associated with the distribution of effect size.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(fs)effectDistributionParamsForTrait([Niso\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(fs)effectSizeDistributionParamsForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 Returns the parameters that configure the distribution of effects for the specified trait or traits. The traits can be specified as +\f4\fs20 \cf2 Returns the parameters that configure the distribution of effect size for the specified trait or traits. The traits can be specified as \f3\fs18 integer \f4\fs20 indices or \f3\fs18 string @@ -7018,10 +7018,10 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 for all other DES types.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(string)effectDistributionTypeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(string)effectSizeDistributionTypeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 Returns the type of distribution of effects for the specified trait or traits. The traits can be specified as +\f4\fs20 \cf2 Returns the type of the distribution of effect size for the specified trait or traits. The traits can be specified as \f3\fs18 integer \f4\fs20 indices or \f3\fs18 string @@ -7048,7 +7048,7 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 class documentation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(io$)loggedData(string$\'a0kind, [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effect\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ +\f3\fs18 \cf2 \'96\'a0(io$)loggedData(string$\'a0kind, [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effectSize\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns mutation data produced by the mutation type\'92s logging facility, as configured by @@ -7083,7 +7083,7 @@ The remaining flags control which columns of data should be returned (for \f4\fs20 object will be returned with named columns of values (or means, or standard deviations) for each specified data column. The \f3\fs18 trait \f4\fs20 property specifies which the traits for which values should be returned, with respect to the -\f3\fs18 effect +\f3\fs18 effectSize \f4\fs20 , \f3\fs18 dominance \f4\fs20 , and @@ -7101,7 +7101,7 @@ The remaining flags control which columns of data should be returned (for \f4\fs20 class documentation.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(void)logMutationData(logical$\'a0enable, [logical$\'a0autogeneratedOnly\'a0=\'a0T], [logical$\'a0meanOnly\'a0=\'a0F], [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effect\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ +\f3\fs18 \cf2 \'96\'a0(void)logMutationData(logical$\'a0enable, [logical$\'a0autogeneratedOnly\'a0=\'a0T], [logical$\'a0meanOnly\'a0=\'a0F], [logical$\'a0id\'a0=\'a0F], [logical$\'a0mutationTypeID\'a0=\'a0F], [logical$\'a0chromosomeID\'a0=\'a0F], [logical$\'a0position\'a0=\'a0F], [logical$\'a0nucleotideValue\'a0=\'a0F], [logical$\'a0originTick\'a0=\'a0F], [logical$\'a0subpopID\'a0=\'a0F], [logical$\'a0tag\'a0=\'a0F], [Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0effectSize\'a0=\'a0F], [logical$\'a0dominance\'a0=\'a0F], [logical$\'a0hemizygousDominance\'a0=\'a0F])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Starts or ends logging of data about new mutations belonging to the target mutation type. If @@ -7188,7 +7188,7 @@ Last come parameters that control the logging of trait-associated data for the n \f4\fs20 objects; \f3\fs18 NULL \f4\fs20 represents all of the traits in the species. For each specified trait, the -\f3\fs18 effect +\f3\fs18 effectSize \f4\fs20 parameter controls logging of the effect size, the \f3\fs18 dominance \f4\fs20 parameter controls the dominance, and the @@ -7198,7 +7198,7 @@ Last come parameters that control the logging of trait-associated data for the n \f4\fs20 and \f3\fs18 weight \f4\fs20 are specified, and the -\f3\fs18 effect=T +\f3\fs18 effectSize=T \f4\fs20 and \f3\fs18 dominance=T \f4\fs20 flags are specified, then columns @@ -7266,10 +7266,10 @@ As for \f4\fs20 is used for each corresponding trait).\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(void)setEffectDistributionForTrait(Niso\'a0trait, string$\'a0distributionType, ...)\ +\f3\fs18 \cf2 \'96\'a0(void)setEffectSizeDistributionForTrait(Niso\'a0trait, string$\'a0distributionType, ...)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 Set the distribution of effects for a specified trait or traits, for the target mutation type. The traits can be specified as +\f4\fs20 \cf2 Set the distribution of effect size for a specified trait or traits, for the target mutation type. The traits can be specified as \f3\fs18 integer \f4\fs20 indices or \f3\fs18 string @@ -7286,27 +7286,27 @@ The \f3\fs18 ... \f4\fs20 should supply a \f3\fs18 numeric$ -\f4\fs20 fixed selection coefficient; +\f4\fs20 fixed effect size; \f3\fs18 "e" \f4\fs20 , in which case the ellipsis should supply a \f3\fs18 numeric$ -\f4\fs20 mean selection coefficient for the exponential distribution; +\f4\fs20 mean effect size for the exponential distribution; \f3\fs18 "g" \f4\fs20 , in which case the ellipsis should supply a \f3\fs18 numeric$ -\f4\fs20 mean selection coefficient and a +\f4\fs20 mean effect size and a \f3\fs18 numeric$ \f4\fs20 alpha shape parameter for a gamma distribution; \f3\fs18 "n" \f4\fs20 , in which case the ellipsis should supply a \f3\fs18 numeric$ -\f4\fs20 mean selection coefficient and a +\f4\fs20 mean effect size and a \f3\fs18 numeric$ \f4\fs20 sigma (standard deviation) parameter for a normal distribution; \f3\fs18 "p" \f4\fs20 , in which case the ellipsis should supply a \f3\fs18 numeric$ -\f4\fs20 mean selection coefficient and a +\f4\fs20 mean effect size and a \f3\fs18 numeric$ \f4\fs20 scale parameter for a Laplace distribution; \f3\fs18 "w" @@ -7322,7 +7322,7 @@ The \f3\fs18 string$ \f4\fs20 Eidos script parameter. See the \f3\fs18 MutationType -\f4\fs20 class documentation for discussion of these distributions and their uses. The distribution of effects for a mutation type is normally a constant in simulations, so be sure you know what you are doing.\ +\f4\fs20 class documentation for discussion of these distributions and their uses. The distribution of effect size for a mutation type is normally a constant in simulations, so be sure you know what you are doing.\ \pard\pardeftab720\ri720\sb360\sa60\partightenfactor0 \f0\b\fs22 \cf2 5.12 Class Plot\ @@ -14662,16 +14662,16 @@ Note that this method is only for use in nonWF models, in which migration is man \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f2\i\fs18 \cf2 -\f3\i0 Effect => (float$)\ +\f3\i0 EffectSize => (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 This dynamic property provides the effect size of the substitution for the trait named \f2\i\fs18 \f4\i0\fs20 . A dynamic property with this naming pattern will be defined for each trait in each species. The -\f3\fs18 effect +\f3\fs18 effectSize \f4\fs20 property and the -\f3\fs18 effectForTrait() -\f4\fs20 method provide alternative ways to access effect values.\ +\f3\fs18 effectSizeForTrait() +\f4\fs20 method provide alternative ways to access effect sizes.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f2\i\fs18 \cf2 @@ -14709,18 +14709,18 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 to access the dominance for that trait.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 effect => (float)\ +\f3\fs18 \cf2 effectSize => (float)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The selection coefficient(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the -\f3\fs18 effectForTrait() +\f4\fs20 \cf2 The effect size(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 effectSizeForTrait() \f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named \f3\fs18 height \f4\fs20 , for example, then \f3\fs18 Substitution \f4\fs20 objects will have a dynamic property named -\f3\fs18 heightEffect -\f4\fs20 to access the effect for that trait.\ +\f3\fs18 heightEffectSize +\f4\fs20 to access the effect size for that trait.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 hemizygousDominance => (float)\ @@ -14869,7 +14869,7 @@ Note that this method is only for use in nonWF models, in which migration is man \f5\fs22 \cf0 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \'96\'a0(float)effectForTrait([Niso\'a0trait\'a0=\'a0NULL])\ +\f3\fs18 \cf2 \'96\'a0(float)effectSizeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 Returns the substitution\'92s effect size for the trait(s) specified by @@ -14886,7 +14886,7 @@ Note that this method is only for use in nonWF models, in which migration is man \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL -\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effects for a given target substitution will be returned consecutively in the order in which the traits are specified by +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effect sizes for a given target substitution will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait \f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 78097c1a..7cd26fd7 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -887,8 +887,7 @@ This function is written in Eidos, and its source code can be viewed with \f2\fs20 to specify an ID of 5). The global symbol for the new mutation type, such as \f1\fs18 m5 \f2\fs20 , is immediately available; the return value also provides the new object.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 The +The \f1\fs18 dominanceCoeff \f2\fs20 parameter supplies the default dominance coefficient for the mutation type, for all traits; \f1\fs18 0.0 @@ -909,11 +908,10 @@ This function is written in Eidos, and its source code can be viewed with \f2\fs20 , but the \f1\fs18 setDefaultHemizygousDominanceForTrait() \f2\fs20 method can configure it subsequently if desired.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 The +The \f1\fs18 distributionType \f2\fs20 and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits. The DES for the mutation type for a specific trait can subsequently be separately configured with the -\f1\fs18 setEffectDistributionForTrait() +\f1\fs18 setEffectSizeDistributionForTrait() \f2\fs20 method if desired. The \f1\fs18 distributionType \f2\fs20 parameter may be @@ -960,7 +958,7 @@ This function is written in Eidos, and its source code can be viewed with \f1\fs18 distributionType \f2\fs20 is \f1\fs18 NULL -\f2\fs20 (the default), a fixed effect of +\f2\fs20 (the default), a fixed effect size of \f1\fs18 0.0 \f2\fs20 is used, representing a neutral DES; this is equivalent to type \f1\fs18 "f" @@ -969,8 +967,7 @@ This function is written in Eidos, and its source code can be viewed with \f2\fs20 is assumed and must not be supplied. See the \f1\fs18 MutationType \f2\fs20 class documentation for discussion of the various DESs and their uses.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 \expnd0\expndtw0\kerning0 +\expnd0\expndtw0\kerning0 Note that by default in WF models, all mutations of a given mutation type will be converted into \f1\fs18 Substitution \f2\fs20 objects when they reach fixation, for efficiency reasons. If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the @@ -1351,7 +1348,6 @@ If \f1\fs18 initializeChromosome() \f2\fs20 , allowing a different mutation run count to be specified for each chromosome in multi-chromosome models.\expnd0\expndtw0\kerning0 \ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 \kerning1\expnd0\expndtw0 If \f1\fs18 preventIncidentalSelfing \f2\fs20 is @@ -1431,7 +1427,6 @@ If \f2\fs20 for \f1\fs18 checkInfiniteLoops \f2\fs20 to disable these checks. There is no way to turn these checks on or off for individual loops; it is a global setting.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 This function will likely be extended with further options in the future, added on to the end of the argument list. Using named arguments with this call is recommended for readability. Note that turning on optional features may increase the runtime and memory footprint of SLiM.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -3045,7 +3040,7 @@ This function assumes that mutations of type \f1\fs18 mutType \f2\fs20 encode their effect size upon the quantitative trait in their \f1\fs18 effect -\f2\fs20 property, as is fairly standard in SLiM. The implementation of +\f2\fs20 property, as is standard in SLiM. The implementation of \f1\fs18 calcVA() \f2\fs20 , which is viewable with \f1\fs18 functionSource() diff --git a/SLiMgui/SLiMWindowController.mm b/SLiMgui/SLiMWindowController.mm index c663a3e0..9abc298f 100644 --- a/SLiMgui/SLiMWindowController.mm +++ b/SLiMgui/SLiMWindowController.mm @@ -4419,7 +4419,7 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu std::advance(mutTypeIter, rowIndex); slim_objectid_t mutTypeID = mutTypeIter->first; MutationType *mutationType = mutTypeIter->second; - EffectDistributionInfo &DES_info = mutationType->effect_distributions_[0]; // FIXME MULTITRAIT + EffectSizeDistributionInfo &DES_info = mutationType->effect_size_distributions_[0]; // FIXME MULTITRAIT if (aTableColumn == mutTypeIDColumn) { diff --git a/VERSIONS b/VERSIONS index 78aeec04..362eb783 100644 --- a/VERSIONS +++ b/VERSIONS @@ -69,7 +69,7 @@ multitrait branch: change MutationType method drawSelectionCoefficient() to drawEffectForTrait([Nio trait = NULL], [integer$ n = 1]) add SLiMgui autofixing for all of the above changes in QtSLiMWindow::checkTerminationForAutofix() add MutationType method setDefaultDominanceForTrait(Nio trait, float dominance) (approximately replacing writing into the dominanceCoeff property, but this should not autofix) - transition MutationType's internals to keep a separate DE for each trait using a new EffectDistributionInfo struct + transition MutationType's internals to keep a separate DE for each trait using a new EffectSizeDistributionInfo struct added C++ all_neutral_DES_ and all_neutral_mutations_ flags to represent whether (a) all of the effects of a given mutation type are neutral, and (b) all mutations of that type are actually neutral make initializeMutationType()'s DES set up the DES for all traits (then separately configurable with setEffectDistributionForTrait()) add support in Individual for the individual's offset for each trait @@ -148,6 +148,21 @@ multitrait branch: re-enable non-neutral caching (but without independent dominance optimizations, so far) add calculation and use of independent dominance caches, per trait add a dynamic property to Individual for accessing individual offsets, Offset + renaming of Mutation's term "effect" to "effectSize" to disambiguate with the "effect" of a mutation (e.g., mutationEffect() callbacks): + Mutation property Effect -> EffectSize + Mutation property effect -> effectSize + Mutation effectForTrait() method -> effectSizeForTrait() + Mutation setEffectForTrait() method -> setEffectSizeForTrait() + MutationType drawEffectForTrait() method -> drawEffectSizeForTrait() + MutationType effectDistributionParamsForTrait() method -> effectSizeDistributionParamsForTrait() + MutationType effectDistributionTypeForTrait() method -> effectSizeDistributionTypeForTrait() + MutationType setEffectDistributionForTrait() method -> setEffectSizeDistributionTypeForTrait() + Substitution Effect property -> EffectSize + Substitution effect property -> effectSize + Substitution effectForTrait() method -> effectSizeForTrait() + addNewMutation() parameter "effect" -> "effectSize" + setEffectSizeForTrait() parameter "effect" -> "effectSize" + MutationType's loggedData() parameter "effect" -> "effectSize" and lots of related fallout for loggedData() and logMutationData(), logged_effect_, running_effect_, etc. version 5.1 (Eidos version 4.1): diff --git a/core/haplosome.cpp b/core/haplosome.cpp index fc80e1fe..239c9e66 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -2273,7 +2273,7 @@ const std::vector *Haplosome_Class::Methods(void) cons methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_addMutations, kEidosValueMaskVOID))->AddObject("mutations", gSLiM_Mutation_Class)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_addNewDrawnMutation, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject("mutationType", gSLiM_MutationType_Class)->AddInt("position")->AddIntObject_ON("originSubpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddIntString_ON("nucleotide", gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_addNewMutation, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject("mutationType", gSLiM_MutationType_Class)->AddNumeric("effect")->AddInt("position")->AddIntObject_ON("originSubpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddIntString_ON("nucleotide", gStaticEidosValueNULL)); // FIXME MULTITRAIT + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_addNewMutation, kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject("mutationType", gSLiM_MutationType_Class)->AddNumeric("effectSize")->AddInt("position")->AddIntObject_ON("originSubpop", gSLiM_Subpopulation_Class, gStaticEidosValueNULL)->AddIntString_ON("nucleotide", gStaticEidosValueNULL)); // FIXME MULTITRAIT methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_containsMarkerMutation, kEidosValueMaskLogical | kEidosValueMaskSingleton | kEidosValueMaskNULL | kEidosValueMaskObject, gSLiM_Mutation_Class))->AddIntObject_S("mutType", gSLiM_MutationType_Class)->AddInt_S("position")->AddLogical_OS("returnMutation", gStaticEidosValue_LogicalF))->DeclareAcceleratedImp(Haplosome::ExecuteMethod_Accelerated_containsMarkerMutation)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_containsMutations, kEidosValueMaskLogical))->AddObject("mutations", gSLiM_Mutation_Class))->DeclareAcceleratedImp(Haplosome::ExecuteMethod_Accelerated_containsMutations)); methods->emplace_back(((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_countOfMutationsOfType, kEidosValueMaskInt | kEidosValueMaskSingleton))->AddIntObject_S("mutType", gSLiM_MutationType_Class))->DeclareAcceleratedImp(Haplosome::ExecuteMethod_Accelerated_countOfMutationsOfType)); @@ -2616,7 +2616,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addMutations(EidosGlobalStringID p_ } // ********************* + (object)addNewDrawnMutation(io mutationType, integer position, [Nio originSubpop = NULL], [Nis nucleotide = NULL]) -// ********************* + (object)addNewMutation(io mutationType, numeric effect, integer position, [Nio originSubpop = NULL], [Nis nucleotide = NULL]) +// ********************* + (object)addNewMutation(io mutationType, numeric effectSize, integer position, [Nio originSubpop = NULL], [Nis nucleotide = NULL]) // EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { @@ -2627,7 +2627,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID #endif EidosValue *arg_muttype = p_arguments[0].get(); - EidosValue *arg_effect = (p_method_id == gID_addNewDrawnMutation ? nullptr : p_arguments[1].get()); + EidosValue *arg_effectSize = (p_method_id == gID_addNewDrawnMutation ? nullptr : p_arguments[1].get()); EidosValue *arg_position = (p_method_id == gID_addNewDrawnMutation ? p_arguments[1].get() : p_arguments[2].get()); EidosValue *arg_origin_subpop = (p_method_id == gID_addNewDrawnMutation ? p_arguments[2].get() : p_arguments[3].get()); EidosValue *arg_nucleotide = (p_method_id == gID_addNewDrawnMutation ? p_arguments[3].get() : p_arguments[4].get()); @@ -2739,7 +2739,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // position and originSubpop can now be either singletons or vectors of matching length or NULL; check them all int muttype_count = arg_muttype->Count(); - int effect_count = (arg_effect ? arg_effect->Count() : 0); + int effectSize_count = (arg_effectSize ? arg_effectSize->Count() : 0); int position_count = arg_position->Count(); int origin_subpop_count = arg_origin_subpop->Count(); int nucleotide_count = arg_nucleotide->Count(); @@ -2749,14 +2749,14 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID if (arg_nucleotide->Type() == EidosValueType::kValueNULL) nucleotide_count = 1; - int count_to_add = std::max({muttype_count, effect_count, position_count, origin_subpop_count, nucleotide_count}); + int count_to_add = std::max({muttype_count, effectSize_count, position_count, origin_subpop_count, nucleotide_count}); if (((muttype_count != 1) && (muttype_count != count_to_add)) || - (arg_effect && (effect_count != 1) && (effect_count != count_to_add)) || + (arg_effectSize && (effectSize_count != 1) && (effectSize_count != count_to_add)) || ((position_count != 1) && (position_count != count_to_add)) || ((origin_subpop_count != 1) && (origin_subpop_count != count_to_add)) || ((nucleotide_count != 1) && (nucleotide_count != count_to_add))) - EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_addNewMutation): " << method_name << " requires that mutationType, " << ((p_method_id == gID_addNewMutation) ? "effect, " : "") << "position, originSubpop, and nucleotide be either (1) singleton, or (2) equal in length to the other non-singleton argument(s), or (3) NULL, for originSubpop and nucleotide." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_addNewMutation): " << method_name << " requires that mutationType, " << ((p_method_id == gID_addNewMutation) ? "effectSize, " : "") << "position, originSubpop, and nucleotide be either (1) singleton, or (2) equal in length to the other non-singleton argument(s), or (3) NULL, for originSubpop and nucleotide." << EidosTerminate(); EidosValue_Object_SP retval(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Mutation_Class)); @@ -2867,7 +2867,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // for the singleton case for each of the parameters, get all the info MutationType *singleton_mutation_type_ptr = SLiM_ExtractMutationTypeFromEidosValue_io(arg_muttype, 0, &community, species, method_name.c_str()); // SPECIES CONSISTENCY CHECK - slim_effect_t singleton_selection_coeff = (arg_effect ? (slim_effect_t)arg_effect->NumericAtIndex_NOCAST(0, nullptr) : (slim_effect_t)0.0); + slim_effect_t singleton_selection_coeff = (arg_effectSize ? (slim_effect_t)arg_effectSize->NumericAtIndex_NOCAST(0, nullptr) : (slim_effect_t)0.0); slim_position_t singleton_position = SLiMCastToPositionTypeOrRaise(arg_position->IntAtIndex_NOCAST(0, nullptr)); @@ -2965,15 +2965,15 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID { slim_effect_t selection_coeff = singleton_selection_coeff; - if (effect_count != 1) + if (effectSize_count != 1) { - if (arg_effect) - selection_coeff = (slim_effect_t)arg_effect->NumericAtIndex_NOCAST(mut_parameter_index, nullptr); + if (arg_effectSize) + selection_coeff = (slim_effect_t)arg_effectSize->NumericAtIndex_NOCAST(mut_parameter_index, nullptr); else - selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT + selection_coeff = mutation_type_ptr->DrawEffectSizeForTrait(0); // FIXME MULTITRAIT } - // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... and hemizygous dominance... + // FIXME MULTITRAIT: This needs to pass in a whole vector of effect sizes and dominance coefficients now... and hemizygous dominance... // FIXME MULTITRAIT this code will also now need to handle the independent dominance case new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), origin_subpop_id, origin_tick, (int8_t)nucleotide); } @@ -3450,7 +3450,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr for (int mut_index = 0; mut_index < segsites; ++mut_index) { slim_position_t position = positions[mut_index]; - slim_effect_t selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT + slim_effect_t selection_coeff = mutation_type_ptr->DrawEffectSizeForTrait(0); // FIXME MULTITRAIT slim_objectid_t subpop_index = -1; slim_tick_t origin_tick = community.Tick(); int8_t nucleotide = -1; @@ -3468,7 +3468,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromMS(EidosGlobalStr MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); - // FIXME MULTITRAIT: This needs to pass in a whole vector of effects and dominance coefficients now... and hemizygous dominance... + // FIXME MULTITRAIT: This needs to pass in a whole vector of effect sizes and dominance coefficients now... and hemizygous dominance... // FIXME MULTITRAIT this code will also now need to handle the independent dominance case Mutation *new_mut = new (mut_block_ptr + new_mut_index) Mutation(mutation_type_ptr, chromosome->Index(), position, static_cast(selection_coeff), mutation_type_ptr->DefaultDominanceForTrait(0), subpop_index, origin_tick, nucleotide); @@ -3786,7 +3786,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt // parse/validate the INFO fields that we recognize std::vector info_substrs = Eidos_string_split(info_str, ";"); std::vector info_mutids; - std::vector info_effects; + std::vector info_effect_sizes; std::vector info_domcoeffs; std::vector info_poporigin; std::vector info_tickorigin; @@ -3824,7 +3824,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt std::vector value_substrs = Eidos_string_split(info_substr.substr(2), ","); for (std::string &value_substr : value_substrs) - info_effects.emplace_back(EidosInterpreter::FloatForString(value_substr, nullptr)); + info_effect_sizes.emplace_back(EidosInterpreter::FloatForString(value_substr, nullptr)); } else if (info_DOM_defined && (info_substr.compare(0, 4, "DOM=") == 0)) // Dominance Coefficient { @@ -3878,7 +3878,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt if ((info_mutids.size() != 0) && (info_mutids.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file unexpected value count for MID field." << EidosTerminate(); - if ((info_effects.size() != 0) && (info_effects.size() != alt_allele_count)) + if ((info_effect_sizes.size() != 0) && (info_effect_sizes.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file unexpected value count for S field." << EidosTerminate(); if ((info_domcoeffs.size() != 0) && (info_domcoeffs.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file unexpected value count for DOM field." << EidosTerminate(); @@ -4026,10 +4026,10 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt // get the selection coefficient from S, or draw one from the mutation type slim_effect_t selection_coeff; - if (info_effects.size() > 0) - selection_coeff = info_effects[alt_allele_index]; + if (info_effect_sizes.size() > 0) + selection_coeff = info_effect_sizes[alt_allele_index]; else - selection_coeff = static_cast(mutation_type_ptr->DrawEffectForTrait(0)); // FIXME MULTITRAIT + selection_coeff = static_cast(mutation_type_ptr->DrawEffectSizeForTrait(0)); // FIXME MULTITRAIT // get the subpop index from PO, or set to -1; no bounds checking on this slim_objectid_t subpop_index = -1; diff --git a/core/individual.cpp b/core/individual.cpp index 83a365fd..c76d53a2 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -5509,7 +5509,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal // parse/validate the INFO fields that we recognize std::vector info_substrs = Eidos_string_split(info_str, ";"); std::vector info_mutids; - std::vector info_effects; + std::vector info_effect_sizes; std::vector info_domcoeffs; std::vector info_poporigin; std::vector info_tickorigin; @@ -5547,7 +5547,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal std::vector value_substrs = Eidos_string_split(info_substr.substr(2), ","); for (std::string &value_substr : value_substrs) - info_effects.emplace_back(EidosInterpreter::FloatForString(value_substr, nullptr)); + info_effect_sizes.emplace_back(EidosInterpreter::FloatForString(value_substr, nullptr)); } else if (info_DOM_defined && (info_substr.compare(0, 4, "DOM=") == 0)) // Dominance Coefficient { @@ -5601,7 +5601,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if ((info_mutids.size() != 0) && (info_mutids.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file unexpected value count for MID field." << EidosTerminate(); - if ((info_effects.size() != 0) && (info_effects.size() != alt_allele_count)) + if ((info_effect_sizes.size() != 0) && (info_effect_sizes.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file unexpected value count for S field." << EidosTerminate(); if ((info_domcoeffs.size() != 0) && (info_domcoeffs.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file unexpected value count for DOM field." << EidosTerminate(); @@ -5646,10 +5646,10 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal // get the selection coefficient from S, or draw one from the mutation type slim_effect_t selection_coeff; - if (info_effects.size() > 0) - selection_coeff = info_effects[alt_allele_index]; + if (info_effect_sizes.size() > 0) + selection_coeff = info_effect_sizes[alt_allele_index]; else - selection_coeff = mutation_type_ptr->DrawEffectForTrait(0); // FIXME MULTITRAIT + selection_coeff = mutation_type_ptr->DrawEffectSizeForTrait(0); // FIXME MULTITRAIT // get the subpop index from PO, or set to -1; no bounds checking on this slim_objectid_t subpop_index = -1; diff --git a/core/mutation.cpp b/core/mutation.cpp index 5e57c92a..fdac216d 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -62,7 +62,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ slim_trait_index_t trait_count = mutation_block->trait_count_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); - // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since + // Below basically does the work of calling SetEffectSize() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. is_neutral_for_all_traits_ = true; // will be set to false below as needed @@ -80,15 +80,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // FIXME MULTITRAIT: This constructor needs to change to have a whole vector of trait information passed in, for effect and dominance // for now we use the values passed in for trait 0, and make other traits neutral - slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : (slim_effect_t)0.0; + slim_effect_t effect_size = (trait_index == 0) ? p_selection_coeff : (slim_effect_t)0.0; slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : (slim_effect_t)0.5; // can be NAN slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably - traitInfoRec->effect_size_ = effect; + traitInfoRec->effect_size_ = effect_size; traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; - if (effect != (slim_effect_t)0.0) + if (effect_size != (slim_effect_t)0.0) { is_neutral_for_all_traits_ = false; @@ -105,15 +105,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (traitType == TraitType::kMultiplicative) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect_size); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect_size); } else // (traitType == TraitType::kAdditive) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect_size); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect_size); } } else // (effect == 0.0) @@ -172,7 +172,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ slim_trait_index_t trait_count = mutation_block->trait_count_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); - // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since + // Below basically does the work of calling SetEffectSize() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. is_neutral_for_all_traits_ = true; // will be set to false below as needed @@ -221,15 +221,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ Trait *trait = traits[trait_index]; TraitType traitType = trait->Type(); - slim_effect_t effect = mutation_type_ptr_->DrawEffectForTrait(trait_index); + slim_effect_t effect_size = mutation_type_ptr_->DrawEffectSizeForTrait(trait_index); slim_effect_t dominance = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); // can be NAN slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); - traitInfoRec->effect_size_ = effect; + traitInfoRec->effect_size_ = effect_size; traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; - if (effect != (slim_effect_t)0.0) + if (effect_size != (slim_effect_t)0.0) { is_neutral_for_all_traits_ = false; @@ -246,15 +246,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (traitType == TraitType::kMultiplicative) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect_size); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect_size); } else // (traitType == TraitType::kAdditive) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect_size); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect_size); } } else // (effect == 0.0) @@ -310,7 +310,7 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ slim_trait_index_t trait_count = mutation_block->trait_count_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); - // Below basically does the work of calling SetEffect() and SetDominance(), more efficiently since + // Below basically does the work of calling SetEffectSize() and SetDominance(), more efficiently since // this is critical path. See those methods for more comments on what is happening here. is_neutral_for_all_traits_ = true; // will be set to false below as needed @@ -328,15 +328,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ // FIXME MULTITRAIT: This constructor needs to change to have a whole vector of trait information passed in, for effect and dominance // for now we use the values passed in for trait 0, and make other traits neutral - slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : (slim_effect_t)0.0; + slim_effect_t effect_size = (trait_index == 0) ? p_selection_coeff : (slim_effect_t)0.0; slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : (slim_effect_t)0.5; // can be NAN slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably - traitInfoRec->effect_size_ = effect; + traitInfoRec->effect_size_ = effect_size; traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; - if (effect != (slim_effect_t)0.0) + if (effect_size != (slim_effect_t)0.0) { is_neutral_for_all_traits_ = false; @@ -353,15 +353,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (traitType == TraitType::kMultiplicative) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect_size); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect_size); } else // (traitType == TraitType::kAdditive) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect_size); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect_size); } } else // (effect == 0.0) @@ -570,7 +570,7 @@ slim_effect_t Mutation::RealizedDominanceForTrait(Trait *p_trait) const } // This should be called whenever a mutation effect is changed; it handles the necessary recaching -void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect) +void Mutation::SetEffectSize(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect) { slim_effect_t old_effect = traitInfoRec->effect_size_; @@ -752,7 +752,7 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(origin_tick_)); case gID_position: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(position_)); - case gID_effect: + case gID_effectSize: { // This is not accelerated, because it's a bit tricky; each mutation could belong to a different species, // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. @@ -771,9 +771,9 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { - slim_effect_t effect = mut_trait_info[trait_index].effect_size_; + slim_effect_t effect_size = mut_trait_info[trait_index].effect_size_; - float_result->push_float_no_check((double)effect); + float_result->push_float_no_check((double)effect_size); } return EidosValue_SP(float_result); @@ -885,16 +885,16 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: - // Here we implement a special behavior: you can do mutation.Effect, mutation.Dominance, + // Here we implement a special behavior: you can do mutation.EffectSize, mutation.Dominance, // and mutation.HemizygousDominance to access a trait's values directly. // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = mutation_type_ptr_->species_; const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); - if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) + if ((property_string.length() > 10) && Eidos_string_hasSuffix(property_string, "EffectSize")) { - std::string trait_name = property_string.substr(0, property_string.length() - 6); + std::string trait_name = property_string.substr(0, property_string.length() - 10); Trait *trait = species.TraitFromName(trait_name); if (trait) @@ -1168,7 +1168,7 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & default: { - // Here we implement a special behavior: you can do mutation.Effect, mutation.Dominance, + // Here we implement a special behavior: you can do mutation.EffectSize, mutation.Dominance, // and mutation.HemizygousDominance to access a trait's values directly. // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). @@ -1178,9 +1178,9 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); Community &community = species.community_; - if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) + if ((property_string.length() > 10) && Eidos_string_hasSuffix(property_string, "EffectSize")) { - std::string trait_name = property_string.substr(0, property_string.length() - 6); + std::string trait_name = property_string.substr(0, property_string.length() - 10); Trait *trait = species.TraitFromName(trait_name); if (trait) @@ -1199,7 +1199,7 @@ void Mutation::SetProperty(EidosGlobalStringID p_property_id, const EidosValue & if (!std::isfinite(new_effect)) EIDOS_TERMINATION << "ERROR (Mutation::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); - SetEffect(trait, traitInfoRec, new_effect); + SetEffectSize(trait, traitInfoRec, new_effect); #if DEBUG SelfConsistencyCheck(" after setting " + property_string); #endif @@ -1311,7 +1311,7 @@ EidosValue_SP Mutation::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, c { switch (p_method_id) { - case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_effectSizeForTrait: return ExecuteMethod_effectSizeForTrait(p_method_id, p_arguments, p_interpreter); case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_hemizygousDominanceForTrait: return ExecuteMethod_hemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_isIndependentDominanceForTrait: return ExecuteMethod_isIndependentDominanceForTrait(p_method_id, p_arguments, p_interpreter); @@ -1320,9 +1320,9 @@ EidosValue_SP Mutation::ExecuteInstanceMethod(EidosGlobalStringID p_method_id, c } } -// ********************* - (float)effectForTrait([Niso trait = NULL]) +// ********************* - (float)effectSizeForTrait([Niso trait = NULL]) // -EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP Mutation::ExecuteMethod_effectSizeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); @@ -1330,7 +1330,7 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; std::vector trait_indices; - species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectForTrait"); + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectSizeForTrait"); // get the trait info for this mutation MutationBlock *mutation_block = species.SpeciesMutationBlock(); @@ -1339,9 +1339,9 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho if (trait_indices.size() == 1) { slim_trait_index_t trait_index = trait_indices[0]; - slim_effect_t effect = mut_trait_info[trait_index].effect_size_; + slim_effect_t effect_size = mut_trait_info[trait_index].effect_size_; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)effect)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)effect_size)); } else { @@ -1349,9 +1349,9 @@ EidosValue_SP Mutation::ExecuteMethod_effectForTrait(EidosGlobalStringID p_metho for (slim_trait_index_t trait_index : trait_indices) { - slim_effect_t effect = mut_trait_info[trait_index].effect_size_; + slim_effect_t effect_size = mut_trait_info[trait_index].effect_size_; - float_result->push_float_no_check((double)effect); + float_result->push_float_no_check((double)effect_size); } return EidosValue_SP(float_result); @@ -1537,7 +1537,7 @@ std::vector *Mutation_Class::Properties_MUTABLE(void properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideValue, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_nucleotideValue)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_originTick, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_originTick)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_position)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effect, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effectSize, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominance, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_subpopID)->DeclareAcceleratedSet(Mutation::SetProperty_Accelerated_subpopID)); @@ -1559,11 +1559,11 @@ const std::vector *Mutation_Class::Methods(void) const methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectSizeForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_isIndependentDominanceForTrait, kEidosValueMaskLogical))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effect", gStaticEidosValueNULL)); + methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setEffectSizeForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("effectSize", gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); methods->emplace_back((EidosClassMethodSignature *)(new EidosClassMethodSignature(gStr_setHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddNumeric_ON("dominance", gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setMutationType, kEidosValueMaskVOID))->AddIntObject_S("mutType", gSLiM_MutationType_Class)); @@ -1578,7 +1578,7 @@ EidosValue_SP Mutation_Class::ExecuteClassMethod(EidosGlobalStringID p_method_id { switch (p_method_id) { - case gID_setEffectForTrait: return ExecuteMethod_setEffectForTrait(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setEffectSizeForTrait: return ExecuteMethod_setEffectSizeForTrait(p_method_id, p_target, p_arguments, p_interpreter); case gID_setDominanceForTrait: case gID_setHemizygousDominanceForTrait: return ExecuteMethod_setDominanceForTrait(p_method_id, p_target, p_arguments, p_interpreter); default: @@ -1586,16 +1586,16 @@ EidosValue_SP Mutation_Class::ExecuteClassMethod(EidosGlobalStringID p_method_id } } -// ********************* + (void)setEffectForTrait([Niso trait = NULL], [Nif effect = NULL]) +// ********************* + (void)setEffectSizeForTrait([Niso trait = NULL], [Nif effectSize = NULL]) // -EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +EidosValue_SP Mutation_Class::ExecuteMethod_setEffectSizeForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { #pragma unused (p_method_id, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); - EidosValue *effect_value = p_arguments[1].get(); + EidosValue *effectSize_value = p_arguments[1].get(); int mutations_count = p_target->Count(); - int effect_count = effect_value->Count(); + int effectSize_count = effectSize_value->Count(); if (mutations_count == 0) return gStaticEidosValueVOID; @@ -1606,7 +1606,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI Species *species = Community::SpeciesForMutations(p_target); if (!species) - EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): setEffectForTrait() requires that all mutations belong to the same species." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectSizeForTrait): setEffectSizeForTrait() requires that all mutations belong to the same species." << EidosTerminate(); MutationBlock *mutation_block = species->SpeciesMutationBlock(); const std::vector &traits = species->Traits(); @@ -1616,16 +1616,16 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI // the goal here is to prevent actions that screw with the tick cycle stage plan that SLiM has already made // in particular, we want to be able to plan trait/fitness optimizations based upon the current milieu if (species->InsideTraitOrFitnessCalculation()) - EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): mutation effects may not be changed within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectSizeForTrait): mutation effects may not be changed within the context of a call to demandPhenotype(), demandPhenotypeForIndividuals(), or recalculateFitness()." << EidosTerminate(); if (species->Active() && ((community.CycleStage() == SLiMCycleStage::kWFStage6CalculateFitness) || (community.CycleStage() == SLiMCycleStage::kNonWFStage3CalculateFitness))) - EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): mutation effects may not be changed during the fitness recalculation tick cycle stage." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectSizeForTrait): mutation effects may not be changed during the fitness recalculation tick cycle stage." << EidosTerminate(); // get the trait indices, with bounds-checking std::vector trait_indices; - species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setEffectForTrait"); + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setEffectSizeForTrait"); slim_trait_index_t trait_count = (slim_trait_index_t)trait_indices.size(); - if (effect_value->Type() == EidosValueType::kValueNULL) + if (effectSize_value->Type() == EidosValueType::kValueNULL) { // pattern 1: drawing a default effect value for each trait in one or more mutations for (slim_trait_index_t trait_index : trait_indices) @@ -1636,16 +1636,16 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationType *muttype = mut->mutation_type_ptr_; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - slim_effect_t effect = (slim_effect_t)muttype->DrawEffectForTrait(trait_index); + slim_effect_t effect_size = (slim_effect_t)muttype->DrawEffectSizeForTrait(trait_index); - mut->SetEffect(traits[trait_index], traitInfoRec, effect); + mut->SetEffectSize(traits[trait_index], traitInfoRec, effect_size); } } } - else if (effect_count == 1) + else if (effectSize_count == 1) { // pattern 2: setting a single effect value across one or more traits in one or more mutations - slim_effect_t effect = static_cast(effect_value->NumericAtIndex_NOCAST(0, nullptr)); + slim_effect_t effect_size = static_cast(effectSize_value->NumericAtIndex_NOCAST(0, nullptr)); if (trait_count == 1) { @@ -1658,7 +1658,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut->SetEffect(traits[trait_index], traitInfoRec, effect); + mut->SetEffectSize(traits[trait_index], traitInfoRec, effect_size); } } else @@ -1672,19 +1672,19 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut->SetEffect(traits[trait_index], traitInfoRec, effect); + mut->SetEffectSize(traits[trait_index], traitInfoRec, effect_size); } } } } - else if (effect_count == trait_count) + else if (effectSize_count == trait_count) { // pattern 3: setting one effect value per trait, in one or more mutations int effect_index = 0; for (slim_trait_index_t trait_index : trait_indices) { - slim_effect_t effect = static_cast(effect_value->NumericAtIndex_NOCAST(effect_index++, nullptr)); + slim_effect_t effect_size = static_cast(effectSize_value->NumericAtIndex_NOCAST(effect_index++, nullptr)); for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) { @@ -1692,18 +1692,18 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - mut->SetEffect(traits[trait_index], traitInfoRec, effect); + mut->SetEffectSize(traits[trait_index], traitInfoRec, effect_size); } } } - else if (effect_count == trait_count * mutations_count) + else if (effectSize_count == trait_count * mutations_count) { // pattern 4: setting different effect values for each trait in each mutation; in this case, // all effects for the specified traits in a given mutation are given consecutively - if (effect_value->Type() == EidosValueType::kValueInt) + if (effectSize_value->Type() == EidosValueType::kValueInt) { // integer effect values - const int64_t *effects_int = effect_value->IntData(); + const int64_t *effect_sizes_int = effectSize_value->IntData(); if (trait_count == 1) { @@ -1715,9 +1715,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI Mutation *mut = mutations_buffer[mutation_index]; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - slim_effect_t effect = static_cast(*(effects_int++)); + slim_effect_t effect_size = static_cast(*(effect_sizes_int++)); - mut->SetEffect(traits[trait_index], traitInfoRec, effect); + mut->SetEffectSize(traits[trait_index], traitInfoRec, effect_size); } } else @@ -1730,9 +1730,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI for (slim_trait_index_t trait_index : trait_indices) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - slim_effect_t effect = static_cast(*(effects_int++)); + slim_effect_t effect_size = static_cast(*(effect_sizes_int++)); - mut->SetEffect(traits[trait_index], traitInfoRec, effect); + mut->SetEffectSize(traits[trait_index], traitInfoRec, effect_size); } } } @@ -1740,7 +1740,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI else { // float effect values - const double *effects_float = effect_value->FloatData(); + const double *effect_sizes_float = effectSize_value->FloatData(); if (trait_count == 1) { @@ -1752,9 +1752,9 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI Mutation *mut = mutations_buffer[mutation_index]; MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - slim_effect_t effect = static_cast(*(effects_float++)); + slim_effect_t effect_size = static_cast(*(effect_sizes_float++)); - mut->SetEffect(traits[trait_index], traitInfoRec, effect); + mut->SetEffectSize(traits[trait_index], traitInfoRec, effect_size); } } else @@ -1767,20 +1767,20 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setEffectForTrait(EidosGlobalStringI for (slim_trait_index_t trait_index : trait_indices) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; - slim_effect_t effect = static_cast(*(effects_float++)); + slim_effect_t effect_size = static_cast(*(effect_sizes_float++)); - mut->SetEffect(traits[trait_index], traitInfoRec, effect); + mut->SetEffectSize(traits[trait_index], traitInfoRec, effect_size); } } } } } else - EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectForTrait): setEffectForTrait() requires that effect be (a) NULL, requesting an effect value drawn from the mutation's mutation type for each trait, (b) singleton, providing one effect value for all traits, (c) equal in length to the number of traits in the species, providing one effect value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one effect value per trait per mutation." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_setEffectSizeForTrait): setEffectSizeForTrait() requires that effect be (a) NULL, requesting an effect value drawn from the mutation's mutation type for each trait, (b) singleton, providing one effect value for all traits, (c) equal in length to the number of traits in the species, providing one effect value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one effect value per trait per mutation." << EidosTerminate(); - // Check for problems; for setEffectForTrait(), it is easier to do sanity checks afterwards than up front + // Check for problems; for setEffectSizeForTrait(), it is easier to do sanity checks afterwards than up front for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) - mutations_buffer[mutation_index]->SelfConsistencyCheck(" after setEffectForTrait()"); + mutations_buffer[mutation_index]->SelfConsistencyCheck(" after setEffectSizeForTrait()"); return gStaticEidosValueVOID; } @@ -2005,7 +2005,7 @@ EidosValue_SP Mutation_Class::ExecuteMethod_setDominanceForTrait(EidosGlobalStri else EIDOS_TERMINATION << "ERROR (Mutation_Class::ExecuteMethod_" << method_name << "): " << method_name << "() requires that dominance be (a) NULL, requesting the default" << ((p_method_id == gID_setDominanceForTrait) ? " " : " hemizygous ") << "dominance coefficient from the mutation's mutation type for each trait, (b) singleton, providing one dominance value for all traits, (c) equal in length to the number of traits in the species, providing one dominance value per trait, or (d) equal in length to the number of traits times the number of target mutations, providing one dominance value per trait per mutation." << EidosTerminate(); - // Check for problems; for setEffectForTrait(), it is easier to do sanity checks afterwards than up front + // Check for problems; for set[Hemizygous]DominanceForTrait(), it is easier to do sanity checks afterwards than up front for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) mutations_buffer[mutation_index]->SelfConsistencyCheck(std::string(" after ") + method_name); diff --git a/core/mutation.h b/core/mutation.h index 2656a75a..abcea5d4 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -182,7 +182,7 @@ class Mutation : public EidosDictionaryRetained slim_effect_t RealizedDominanceForTrait(Trait *p_trait) const; // These should be called whenever a mutation effect/dominance is changed; they handle the necessary recaching - void SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect); + void SetEffectSize(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect); void SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); void SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance); @@ -195,7 +195,7 @@ class Mutation : public EidosDictionaryRetained virtual EidosValue_SP GetProperty(EidosGlobalStringID p_property_id) override; virtual void SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) override; virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; - EidosValue_SP ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_effectSizeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_isIndependentDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); @@ -249,7 +249,7 @@ class Mutation_Class : public EidosDictionaryRetained_Class virtual const std::vector *Methods(void) const override; virtual EidosValue_SP ExecuteClassMethod(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const override; - EidosValue_SP ExecuteMethod_setEffectForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + EidosValue_SP ExecuteMethod_setEffectSizeForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_setDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; EidosValue_SP ExecuteMethod_setHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; }; diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index c43e102e..7b3d6c2f 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -93,7 +93,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr muttype_all_neutral_mutations_ = all_neutral_DES_; // set up DE entries for all traits; every trait is initialized identically, from the parameters given - EffectDistributionInfo DES_info; + EffectSizeDistributionInfo DES_info; DES_info.default_dominance_coeff_ = static_cast(p_dominance_coeff); // note this can be NAN now, representing independent dominance DES_info.default_hemizygous_dominance_coeff_ = 1.0; @@ -102,7 +102,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr DES_info.DES_strings_ = p_DES_strings; for (slim_trait_index_t trait_index = 0; trait_index < species_.TraitCount(); trait_index++) - effect_distributions_.push_back(DES_info); + effect_size_distributions_.push_back(DES_info); // Nucleotide-based mutations use a special stacking group, -1, and always use stacking policy "l" if (p_nuc_based) @@ -117,7 +117,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr MutationType::~MutationType(void) { - for (EffectDistributionInfo &des_info : effect_distributions_) + for (EffectSizeDistributionInfo &des_info : effect_size_distributions_) { delete des_info.cached_DES_script_; des_info.cached_DES_script_ = nullptr; @@ -149,7 +149,7 @@ void MutationType::FreeLoggingInfo(void) for (TraitEffectLog &trait_effect_log : logged_traits_) { - if (trait_effect_log.logged_effect_) { free(trait_effect_log.logged_effect_); trait_effect_log.logged_effect_ = nullptr; } + if (trait_effect_log.logged_effect_size_) { free(trait_effect_log.logged_effect_size_); trait_effect_log.logged_effect_size_ = nullptr; } if (trait_effect_log.logged_dominance_) { free(trait_effect_log.logged_dominance_); trait_effect_log.logged_dominance_ = nullptr; } if (trait_effect_log.logged_hemizygous_dominance_) { free(trait_effect_log.logged_hemizygous_dominance_); trait_effect_log.logged_hemizygous_dominance_ = nullptr; } } @@ -188,7 +188,7 @@ void MutationType::_LogMutationInfo(Mutation *p_mut) { TraitEffectLog &trait_log = logged_traits_[trait_index]; - if (log_effect_) trait_log.logged_effect_ = (slim_effect_t *)realloc(trait_log.logged_effect_, log_capacity_ * sizeof(slim_effect_t)); + if (log_effectSize_) trait_log.logged_effect_size_ = (slim_effect_t *)realloc(trait_log.logged_effect_size_, log_capacity_ * sizeof(slim_effect_t)); if (log_dominance_) trait_log.logged_dominance_ = (slim_effect_t *)realloc(trait_log.logged_dominance_, log_capacity_ * sizeof(slim_effect_t)); if (log_hemizygousDominance_) trait_log.logged_hemizygous_dominance_ = (slim_effect_t *)realloc(trait_log.logged_hemizygous_dominance_, log_capacity_ * sizeof(slim_effect_t)); } @@ -241,10 +241,10 @@ void MutationType::_LogMutationInfo(Mutation *p_mut) TraitEffectLog &trait_log = logged_traits_[trait_index]; MutationTraitInfo &mut_trait_info = species_.SpeciesMutationBlock()->TraitInfoForMutation(p_mut)[trait_index]; - if (log_effect_) { - slim_effect_t effect = mut_trait_info.effect_size_; - trait_log.running_effect_ += (double)effect; - if (!log_meanOnly_) trait_log.logged_effect_[log_size_] = effect; + if (log_effectSize_) { + slim_effect_t effect_size = mut_trait_info.effect_size_; + trait_log.running_effect_size_ += (double)effect_size; + if (!log_meanOnly_) trait_log.logged_effect_size_[log_size_] = effect_size; } if (log_dominance_) { slim_effect_t dominance = p_mut->RealizedDominanceForTrait(species_.Traits()[trait_index]); @@ -378,12 +378,12 @@ void MutationType::SelfConsistencyCheck(const std::string &p_message_end) // unlike SelfConsistencyCheck() for Mutation and Substitution, where the mutation block necessarily exists const std::vector &traits = species_.Traits(); - if (effect_distributions_.size() != traits.size()) - EIDOS_TERMINATION << "ERROR (MutationType::SelfConsistencyCheck): (internal error) effect_distributions_ size does not match traits.size()" << p_message_end << "." << EidosTerminate(); + if (effect_size_distributions_.size() != traits.size()) + EIDOS_TERMINATION << "ERROR (MutationType::SelfConsistencyCheck): (internal error) effect_size_distributions_ size does not match traits.size()" << p_message_end << "." << EidosTerminate(); - if (effect_distributions_.size() > 0) + if (effect_size_distributions_.size() > 0) { - for (EffectDistributionInfo &des_info : effect_distributions_) + for (EffectSizeDistributionInfo &des_info : effect_size_distributions_) { if (std::isinf(des_info.default_dominance_coeff_)) // NAN allowed EIDOS_TERMINATION << "ERROR (MutationType::SelfConsistencyCheck): mutation type default dominance is infinite" << p_message_end << "." << EidosTerminate(); @@ -394,9 +394,9 @@ void MutationType::SelfConsistencyCheck(const std::string &p_message_end) } } -slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) const +slim_effect_t MutationType::DrawEffectSizeForTrait(slim_trait_index_t p_trait_index) const { - const EffectDistributionInfo &DES_info = effect_distributions_[p_trait_index]; + const EffectSizeDistributionInfo &DES_info = effect_size_distributions_[p_trait_index]; // BCH 11/11/2022: Note that EIDOS_GSL_RNG(omp_get_thread_num()) can take a little bit of time when running // parallel. We don't want to pass the RNG in, though, because that would slow down the single-threaded @@ -444,9 +444,9 @@ slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) #ifdef DEBUG_LOCKS_ENABLED // When running multi-threaded, this code is not re-entrant because it runs an Eidos interpreter. We use // EidosDebugLock to enforce that. In addition, it can raise, so the caller must be prepared for that. - static EidosDebugLock DrawEffectForTrait_InterpreterLock("DrawEffectForTrait_InterpreterLock"); + static EidosDebugLock DrawEffectSizeForTrait_InterpreterLock("DrawEffectSizeForTrait_InterpreterLock"); - DrawEffectForTrait_InterpreterLock.start_critical(0); + DrawEffectSizeForTrait_InterpreterLock.start_critical(0); #endif double sel_coeff; @@ -476,17 +476,17 @@ slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) if (gEidosTerminateThrows) { gEidosErrorContext = error_context_save; - TranslateErrorContextToUserScript("DrawEffectForTrait()"); + TranslateErrorContextToUserScript("DrawEffectSizeForTrait()"); } delete DES_info.cached_DES_script_; DES_info.cached_DES_script_ = nullptr; #ifdef DEBUG_LOCKS_ENABLED - DrawEffectForTrait_InterpreterLock.end_critical(); + DrawEffectSizeForTrait_InterpreterLock.end_critical(); #endif - EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectForTrait): tokenize/parse error in type 's' DES callback script." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectSizeForTrait): tokenize/parse error in type 's' DES callback script." << EidosTerminate(nullptr); } } @@ -514,7 +514,7 @@ slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) else if ((result_type == EidosValueType::kValueInt) && (result_count == 1)) sel_coeff = result->IntData()[0]; else - EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectForTrait): type 's' DES callbacks must provide a singleton float or integer return value." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectSizeForTrait): type 's' DES callbacks must provide a singleton float or integer return value." << EidosTerminate(nullptr); } catch (...) { @@ -529,12 +529,12 @@ slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) if (!gEidosErrorContext.currentScript || (gEidosErrorContext.currentScript->UserScriptUTF16Offset() == -1)) { gEidosErrorContext = error_context_save; - TranslateErrorContextToUserScript("DrawEffectForTrait()"); + TranslateErrorContextToUserScript("DrawEffectSizeForTrait()"); } } #ifdef DEBUG_LOCKS_ENABLED - DrawEffectForTrait_InterpreterLock.end_critical(); + DrawEffectSizeForTrait_InterpreterLock.end_critical(); #endif throw; @@ -544,13 +544,13 @@ slim_effect_t MutationType::DrawEffectForTrait(slim_trait_index_t p_trait_index) gEidosErrorContext = error_context_save; #ifdef DEBUG_LOCKS_ENABLED - DrawEffectForTrait_InterpreterLock.end_critical(); + DrawEffectSizeForTrait_InterpreterLock.end_critical(); #endif return static_cast(sel_coeff); } } - EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectForTrait): (internal error) unexpected DES_type_ value." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectSizeForTrait): (internal error) unexpected DES_type_ value." << EidosTerminate(); } // This is unused except by debugging code and in the debugger itself @@ -829,14 +829,14 @@ EidosValue_SP MutationType::ExecuteInstanceMethod(EidosGlobalStringID p_method_i { case gID_defaultDominanceForTrait: return ExecuteMethod_defaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_defaultHemizygousDominanceForTrait: return ExecuteMethod_defaultHemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); - case gID_effectDistributionTypeForTrait: return ExecuteMethod_effectDistributionTypeForTrait(p_method_id, p_arguments, p_interpreter); - case gID_effectDistributionParamsForTrait: return ExecuteMethod_effectDistributionParamsForTrait(p_method_id, p_arguments, p_interpreter); - case gID_drawEffectForTrait: return ExecuteMethod_drawEffectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_effectSizeDistributionTypeForTrait: return ExecuteMethod_effectSizeDistributionTypeForTrait(p_method_id, p_arguments, p_interpreter); + case gID_effectSizeDistributionParamsForTrait: return ExecuteMethod_effectSizeDistributionParamsForTrait(p_method_id, p_arguments, p_interpreter); + case gID_drawEffectSizeForTrait: return ExecuteMethod_drawEffectSizeForTrait(p_method_id, p_arguments, p_interpreter); case gID_loggedData: return ExecuteMethod_loggedData(p_method_id, p_arguments, p_interpreter); case gID_logMutationData: return ExecuteMethod_logMutationData(p_method_id, p_arguments, p_interpreter); case gID_setDefaultDominanceForTrait: return ExecuteMethod_setDefaultDominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_setDefaultHemizygousDominanceForTrait: return ExecuteMethod_setDefaultHemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); - case gID_setEffectDistributionForTrait: return ExecuteMethod_setEffectDistributionForTrait(p_method_id, p_arguments, p_interpreter); + case gID_setEffectSizeDistributionForTrait: return ExecuteMethod_setEffectSizeDistributionForTrait(p_method_id, p_arguments, p_interpreter); default: return super::ExecuteInstanceMethod(p_method_id, p_arguments, p_interpreter); } } @@ -897,16 +897,16 @@ EidosValue_SP MutationType::ExecuteMethod_defaultHemizygousDominanceForTrait(Eid } } -// ********************* - (fs)effectDistributionParamsForTrait([Niso trait = NULL]) +// ********************* - (fs)effectSizeDistributionParamsForTrait([Niso trait = NULL]) // -EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_effectSizeDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); // get the trait indices, with bounds-checking std::vector trait_indices; - species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectDistributionParamsForTrait"); + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectSizeDistributionParamsForTrait"); // decide whether doing floats or strings; must be the same for all bool is_float = false; @@ -914,7 +914,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos for (slim_trait_index_t trait_index : trait_indices) { - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = effect_size_distributions_[trait_index]; if (DES_info.DES_parameters_.size() > 0) is_float = true; @@ -923,7 +923,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos } if (is_float && is_string) - EIDOS_TERMINATION << "ERROR (ExecuteMethod_effectDistributionParamsForTrait): effectDistributionParamsForTrait() requires all specified traits to have either float or string parameters (not a mixture) for their distributions of effects." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (ExecuteMethod_effectSizeDistributionParamsForTrait): effectSizeDistributionParamsForTrait() requires all specified traits to have either float or string parameters (not a mixture) for their distributions of effects." << EidosTerminate(nullptr); if (is_float) { @@ -931,7 +931,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos for (slim_trait_index_t trait_index : trait_indices) { - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = effect_size_distributions_[trait_index]; for (double param : DES_info.DES_parameters_) float_result->push_float(param); @@ -945,7 +945,7 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos for (slim_trait_index_t trait_index : trait_indices) { - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = effect_size_distributions_[trait_index]; for (const std::string ¶m : DES_info.DES_strings_) string_result->PushString(param); @@ -955,23 +955,23 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionParamsForTrait(Eidos } } -// ********************* - (string$)effectDistributionTypeForTrait([Niso trait = NULL]) +// ********************* - (string$)effectSizeDistributionTypeForTrait([Niso trait = NULL]) // -EidosValue_SP MutationType::ExecuteMethod_effectDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_effectSizeDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); // get the trait indices, with bounds-checking std::vector trait_indices; - species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectDistributionTypeForTrait"); + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectSizeDistributionTypeForTrait"); // assemble the result EidosValue_String *string_result = new (gEidosValuePool->AllocateChunk()) EidosValue_String(); for (slim_trait_index_t trait_index : trait_indices) { - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = effect_size_distributions_[trait_index]; switch (DES_info.DES_type_) { @@ -989,9 +989,9 @@ EidosValue_SP MutationType::ExecuteMethod_effectDistributionTypeForTrait(EidosGl return EidosValue_SP(string_result); } -// ********************* - (float)drawEffectForTrait([Niso trait = NULL], [integer$ n = 1]) +// ********************* - (float)drawEffectSizeForTrait([Niso trait = NULL], [integer$ n = 1]) // -EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_drawEffectSizeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue_SP result_SP(nullptr); @@ -1000,19 +1000,19 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID // get the trait indices, with bounds-checking std::vector trait_indices; - species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "drawEffectForTrait"); + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "drawEffectSizeForTrait"); // get the number of effects to draw int64_t num_draws = n_value->IntAtIndex_NOCAST(0, nullptr); if (num_draws < 0) - EIDOS_TERMINATION << "ERROR (ExecuteMethod_drawEffectForTrait): drawEffectForTrait() requires n to be greater than or equal to 0 (" << num_draws << " supplied)." << EidosTerminate(nullptr); + EIDOS_TERMINATION << "ERROR (ExecuteMethod_drawEffectSizeForTrait): drawEffectSizeForTrait() requires n to be greater than or equal to 0 (" << num_draws << " supplied)." << EidosTerminate(nullptr); if ((trait_indices.size() == 1) && (num_draws == 1)) { slim_trait_index_t trait_index = trait_indices[0]; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)DrawEffectForTrait(trait_index))); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)DrawEffectSizeForTrait(trait_index))); } else { @@ -1021,7 +1021,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID // draw_index is the outer loop, so that we get num_draws sets of (one draw per trait) for (int64_t draw_index = 0; draw_index < num_draws; ++draw_index) for (slim_trait_index_t trait_index : trait_indices) - float_result->push_float_no_check((double)DrawEffectForTrait(trait_index)); + float_result->push_float_no_check((double)DrawEffectSizeForTrait(trait_index)); return EidosValue_SP(float_result); } @@ -1029,7 +1029,7 @@ EidosValue_SP MutationType::ExecuteMethod_drawEffectForTrait(EidosGlobalStringID // ********************* - (io)loggedData(string$ kind, [logical$ id = F], [logical$ mutationTypeID = F], [logical$ chromosomeID = F], [logical$ position = F], // [logical$ nucleotideValue = F], [logical$ originTick = F], [logical$ subpopID = F], [logical$ tag = F], -// [Niso trait = NULL], [l$ effect = F], [l$ dominance = F], [l$ hemizygousDominance = F]) +// [Niso trait = NULL], [l$ effectSize = F], [l$ dominance = F], [l$ hemizygousDominance = F]) // EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -1044,7 +1044,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho EidosValue *subpopID_value = p_arguments[7].get(); EidosValue *tag_value = p_arguments[8].get(); EidosValue *trait_value = p_arguments[9].get(); - EidosValue *effect_value = p_arguments[10].get(); + EidosValue *effectSize_value = p_arguments[10].get(); EidosValue *dominance_value = p_arguments[11].get(); EidosValue *hemizygousDominance_value = p_arguments[12].get(); @@ -1103,7 +1103,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho bool get_originTick = originTick_value->LogicalAtIndex_NOCAST(0, nullptr); bool get_subpopID = subpopID_value->LogicalAtIndex_NOCAST(0, nullptr); bool get_tag = tag_value->LogicalAtIndex_NOCAST(0, nullptr); - bool get_effect = effect_value->LogicalAtIndex_NOCAST(0, nullptr); + bool get_effectSize = effectSize_value->LogicalAtIndex_NOCAST(0, nullptr); bool get_dominance = dominance_value->LogicalAtIndex_NOCAST(0, nullptr); bool get_hemizygousDominance = hemizygousDominance_value->LogicalAtIndex_NOCAST(0, nullptr); @@ -1111,7 +1111,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho // user can either use the default signature and get everything that was logged, OR pass flag=T to get the // ones they want without having to pass F for others) if (!get_id && !get_mutationTypeID && !get_chromosomeID && !get_position && !get_nucleotideValue && - !get_originTick && !get_subpopID && !get_tag && !get_effect && !get_dominance && !get_hemizygousDominance) + !get_originTick && !get_subpopID && !get_tag && !get_effectSize && !get_dominance && !get_hemizygousDominance) { get_id = log_id_; get_mutationTypeID = log_mutationTypeID_; @@ -1121,7 +1121,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho get_originTick = log_originTick_; get_subpopID = log_subpopID_; get_tag = log_tag_; - get_effect = log_effect_; + get_effectSize = log_effectSize_; get_dominance = log_dominance_; get_hemizygousDominance = log_hemizygousDominance_; } @@ -1135,7 +1135,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho if (!log_originTick_) get_originTick = false; if (!log_subpopID_) get_subpopID = false; if (!log_tag_) get_tag = false; - if (!log_effect_) get_effect = false; + if (!log_effectSize_) get_effectSize = false; if (!log_dominance_) get_dominance = false; if (!log_hemizygousDominance_) get_hemizygousDominance = false; @@ -1257,15 +1257,15 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho TraitEffectLog &trait_log = logged_traits_[trait_index]; const std::string &trait_name = species_.Traits()[trait_index]->Name(); - if (get_effect) + if (get_effectSize) { - if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_log.running_effect_ / log_size_) : gStaticEidosValue_FloatNAN.get(); - else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_effect_, log_size_)); + if (kind == KindEnum::kMean) column = (log_size_ > 0) ? new (gEidosValuePool->AllocateChunk()) EidosValue_Float(trait_log.running_effect_size_ / log_size_) : gStaticEidosValue_FloatNAN.get(); + else if (kind == KindEnum::kSD) column = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(Eidos_StandardDeviation(trait_log.logged_effect_size_, log_size_)); else /* kind == KindEnum::kValues */ { column = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(log_size_); double *column_data = column->FloatData_Mutable(); for (size_t log_index = 0; log_index < log_size_; log_index++) - column_data[log_index] = (double)trait_log.logged_effect_[log_index]; + column_data[log_index] = (double)trait_log.logged_effect_size_[log_index]; } dataframe->SetKeyValue_StringKeys(trait_name + "Effect", EidosValue_SP(column)); } @@ -1305,7 +1305,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho // ********************* - (void)logMutationData(logical$ enable, [logical$ autogeneratedOnly = T], [logical$ meanOnly = F], [logical$ id = F], [logical$ mutationTypeID = F], // [logical$ chromosomeID = F], [logical$ position = F], [logical$ nucleotideValue = F], [logical$ originTick = F], -// [logical$ subpopID = F], [logical$ tag = F], [Niso trait = NULL], [logical$ effect = F], [logical$ dominance = F], +// [logical$ subpopID = F], [logical$ tag = F], [Niso trait = NULL], [logical$ effectSize = F], [logical$ dominance = F], // [logical$ hemizygousDominance = F]) // EidosValue_SP MutationType::ExecuteMethod_logMutationData(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) @@ -1323,7 +1323,7 @@ EidosValue_SP MutationType::ExecuteMethod_logMutationData(EidosGlobalStringID p_ EidosValue *subpopID_value = p_arguments[9].get(); EidosValue *tag_value = p_arguments[10].get(); EidosValue *trait_value = p_arguments[11].get(); - EidosValue *effect_value = p_arguments[12].get(); + EidosValue *effectSize_value = p_arguments[12].get(); EidosValue *dominance_value = p_arguments[13].get(); EidosValue *hemizygousDominance_value = p_arguments[14].get(); @@ -1364,13 +1364,13 @@ EidosValue_SP MutationType::ExecuteMethod_logMutationData(EidosGlobalStringID p_ log_originTick_ = originTick_value->LogicalAtIndex_NOCAST(0, nullptr); log_subpopID_ = subpopID_value->LogicalAtIndex_NOCAST(0, nullptr); log_tag_ = tag_value->LogicalAtIndex_NOCAST(0, nullptr); - log_effect_ = effect_value->LogicalAtIndex_NOCAST(0, nullptr); + log_effectSize_ = effectSize_value->LogicalAtIndex_NOCAST(0, nullptr); log_dominance_ = dominance_value->LogicalAtIndex_NOCAST(0, nullptr); log_hemizygousDominance_ = hemizygousDominance_value->LogicalAtIndex_NOCAST(0, nullptr); if (log_nucleotideValue_ && !species_.IsNucleotideBased()) EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_logMutationData): logging of nucleotide data is only supported in nucleotide-based models." << EidosTerminate(nullptr); - if ((logged_trait_indices.size() == 0) && (log_effect_ || log_dominance_ || log_hemizygousDominance_)) + if ((logged_trait_indices.size() == 0) && (log_effectSize_ || log_dominance_ || log_hemizygousDominance_)) EIDOS_TERMINATION << "ERROR (MutationType::ExecuteMethod_logMutationData): logging of effect, dominance, and hemizygous dominance is enabled, but no traits to log were specified." << EidosTerminate(nullptr); // allocate log pointers and running sums @@ -1398,7 +1398,7 @@ EidosValue_SP MutationType::ExecuteMethod_logMutationData(EidosGlobalStringID p_ { TraitEffectLog &trait_effect_log = logged_traits_[trait_index]; - if (log_effect_) { trait_effect_log.running_effect_ = 0.0; trait_effect_log.logged_effect_ = (slim_effect_t *)malloc(log_capacity_ * sizeof(slim_effect_t)); } + if (log_effectSize_) { trait_effect_log.running_effect_size_ = 0.0; trait_effect_log.logged_effect_size_ = (slim_effect_t *)malloc(log_capacity_ * sizeof(slim_effect_t)); } if (log_dominance_) { trait_effect_log.running_dominance_ = 0.0; trait_effect_log.logged_dominance_ = (slim_effect_t *)malloc(log_capacity_ * sizeof(slim_effect_t)); } if (log_hemizygousDominance_) { trait_effect_log.running_hemizygous_dominance_ = 0.0; trait_effect_log.logged_hemizygous_dominance_ = (slim_effect_t *)malloc(log_capacity_ * sizeof(slim_effect_t)); } } @@ -1428,7 +1428,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba for (slim_trait_index_t trait_index : trait_indices) { - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = effect_size_distributions_[trait_index]; DES_info.default_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check } @@ -1438,7 +1438,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultDominanceForTrait(EidosGloba for (int dominance_index = 0; dominance_index < dominance_count; dominance_index++) { slim_trait_index_t trait_index = trait_indices[dominance_index]; - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = effect_size_distributions_[trait_index]; double dominance = dominance_value->FloatAtIndex_NOCAST(dominance_index, nullptr); DES_info.default_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check @@ -1478,7 +1478,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( for (slim_trait_index_t trait_index : trait_indices) { - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = effect_size_distributions_[trait_index]; DES_info.default_hemizygous_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check } @@ -1488,7 +1488,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( for (int dominance_index = 0; dominance_index < dominance_count; dominance_index++) { slim_trait_index_t trait_index = trait_indices[dominance_index]; - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = effect_size_distributions_[trait_index]; double dominance = dominance_value->FloatAtIndex_NOCAST(dominance_index, nullptr); DES_info.default_hemizygous_dominance_coeff_ = static_cast(dominance); // intentionally no bounds check @@ -1508,9 +1508,9 @@ EidosValue_SP MutationType::ExecuteMethod_setDefaultHemizygousDominanceForTrait( return gStaticEidosValueVOID; } -// ********************* - (void)setEffectDistributionForTrait(Niso trait, string$ distributionType, ...) +// ********************* - (void)setEffectSizeDistributionForTrait(Niso trait, string$ distributionType, ...) // -EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP MutationType::ExecuteMethod_setEffectSizeDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); @@ -1519,7 +1519,7 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo // get the trait indices, with bounds-checking std::vector trait_indices; - species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setEffectDistributionForTrait"); + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setEffectSizeDistributionForTrait"); // Parse the DES type and parameters, and do various sanity checks DESType DES_type; @@ -1531,7 +1531,7 @@ EidosValue_SP MutationType::ExecuteMethod_setEffectDistributionForTrait(EidosGlo // Everything seems to be in order, so replace our distribution info (in each specified trait) with the new info for (slim_trait_index_t trait_index : trait_indices) { - EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = effect_size_distributions_[trait_index]; DES_info.DES_type_ = DES_type; DES_info.DES_parameters_ = DES_parameters; @@ -1593,9 +1593,9 @@ const std::vector *MutationType_Class::Methods(void) c methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_defaultHemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectDistributionTypeForTrait, kEidosValueMaskString))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectSizeDistributionParamsForTrait, kEidosValueMaskFloat | kEidosValueMaskString))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectSizeDistributionTypeForTrait, kEidosValueMaskString))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_drawEffectSizeForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)->AddInt_OS("n", gStaticEidosValue_Integer1)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N(gStr_trait, gSLiM_Trait_Class)->AddFloat("dominance")); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_loggedData, kEidosValueMaskInt | kEidosValueMaskObject | kEidosValueMaskSingleton, gEidosDataFrame_Class)) ->AddString_S("kind") @@ -1608,7 +1608,7 @@ const std::vector *MutationType_Class::Methods(void) c ->AddLogical_OS("subpopID", gStaticEidosValue_LogicalF) ->AddLogical_OS("tag", gStaticEidosValue_LogicalF) ->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL) - ->AddLogical_OS("effect", gStaticEidosValue_LogicalF) + ->AddLogical_OS("effectSize", gStaticEidosValue_LogicalF) ->AddLogical_OS("dominance", gStaticEidosValue_LogicalF) ->AddLogical_OS("hemizygousDominance", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_logMutationData, kEidosValueMaskVOID)) @@ -1624,11 +1624,11 @@ const std::vector *MutationType_Class::Methods(void) c ->AddLogical_OS("subpopID", gStaticEidosValue_LogicalF) ->AddLogical_OS("tag", gStaticEidosValue_LogicalF) ->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL) - ->AddLogical_OS("effect", gStaticEidosValue_LogicalF) + ->AddLogical_OS("effectSize", gStaticEidosValue_LogicalF) ->AddLogical_OS("dominance", gStaticEidosValue_LogicalF) ->AddLogical_OS("hemizygousDominance", gStaticEidosValue_LogicalF)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setDefaultHemizygousDominanceForTrait, kEidosValueMaskVOID))->AddIntStringObject_N(gStr_trait, gSLiM_Trait_Class)->AddFloat("dominance")); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectDistributionForTrait, kEidosValueMaskVOID))->AddIntStringObject_N(gStr_trait, gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_setEffectSizeDistributionForTrait, kEidosValueMaskVOID))->AddIntStringObject_N(gStr_trait, gSLiM_Trait_Class)->AddString_S("distributionType")->AddEllipsis()); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/mutation_type.h b/core/mutation_type.h index 222e53ed..428e74dc 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -20,7 +20,7 @@ /* The class MutationType represents a type of mutation defined in the input file, such as a synonymous mutation or an adaptive mutation. - A particular mutation type is defined by its distribution of effect sizes (DES) and its dominance coefficient. Once a mutation type + A particular mutation type is defined by its distribution of effect size (DES) and its dominance coefficient. Once a mutation type is defined, a draw from its DES can be generated to determine the selection coefficient of a particular mutation of that type. */ @@ -48,7 +48,7 @@ class MutationType_Class; extern MutationType_Class *gSLiM_MutationType_Class; -// This enumeration represents a type of distribution of effect sizes (DES) that a mutation type can draw from +// This enumeration represents a type of distribution of effect size (DES) that a mutation type can draw from enum class DESType : char { kFixed = 0, kGamma, @@ -62,9 +62,9 @@ enum class DESType : char { std::ostream& operator<<(std::ostream& p_out, DESType p_DES_type); -// This struct holds information about a distribution of effects (including dominance) for one trait. +// This struct holds information about a distribution of effect size (including dominance) for one trait. // MutationEffect then keeps a vector of these structs, one for each trait. -typedef struct _EffectDistributionInfo { +typedef struct _EffectSizeDistributionInfo { slim_effect_t default_dominance_coeff_; // the default dominance coefficient (h) inherited by mutations of this type slim_effect_t default_hemizygous_dominance_coeff_; // the default dominance coefficient (h) used when one haplosome is null @@ -72,12 +72,12 @@ typedef struct _EffectDistributionInfo { std::vector DES_parameters_; // DES parameters, of type double (originally float or integer type) std::vector DES_strings_; // DES parameters, of type std::string (originally string type) mutable EidosScript *cached_DES_script_ = nullptr; // used by DES type 's' to hold a cached script for the DES -} EffectDistributionInfo; +} EffectSizeDistributionInfo; // This struct holds per-trait information about mutations, for our mutation data logging facility typedef struct _TraitEffectLog { - double running_effect_, running_dominance_, running_hemizygous_dominance_; - slim_effect_t *logged_effect_ = nullptr; + double running_effect_size_, running_dominance_, running_hemizygous_dominance_; + slim_effect_t *logged_effect_size_ = nullptr; slim_effect_t *logged_dominance_ = nullptr; slim_effect_t *logged_hemizygous_dominance_ = nullptr; } TraitEffectLog; @@ -100,7 +100,7 @@ class MutationType : public EidosDictionaryUnretained bool log_meanOnly_; bool log_id_, log_mutationTypeID_, log_chromosomeID_, log_position_, log_nucleotideValue_, log_originTick_, - log_subpopID_, log_tag_, log_effect_, log_dominance_, log_hemizygousDominance_; + log_subpopID_, log_tag_, log_effectSize_, log_dominance_, log_hemizygousDominance_; size_t log_size_ = 0; // number of entries logged size_t log_capacity_ = 0; // number of entries allocated @@ -125,9 +125,9 @@ class MutationType : public EidosDictionaryUnretained public: - // a mutation type is specified by the distribution of effects (DE) and the default dominance coefficient + // a mutation type is specified by the distribution of effect size (DES) and the default dominance coefficient // - // DE options: f: fixed (s) + // DES options: f: fixed (s) // e: exponential (mean s) // g: gamma distribution (mean s,shape) // @@ -139,7 +139,7 @@ class MutationType : public EidosDictionaryUnretained slim_objectid_t mutation_type_id_; // the id by which this mutation type is indexed in the chromosome EidosValue_SP cached_value_muttype_id_; // a cached value for mutation_type_id_; reset() if that changes - std::vector effect_distributions_; // DESs for each trait in the species + std::vector effect_size_distributions_; // DESs for each trait in the species bool nucleotide_based_; // if true, the mutation type is nucleotide-based (i.e. mutations keep associated nucleotides) @@ -242,19 +242,19 @@ class MutationType : public EidosDictionaryUnretained slim_effect_t DefaultDominanceForTrait(slim_trait_index_t p_trait_index) const { - const EffectDistributionInfo &DES_info = effect_distributions_[p_trait_index]; + const EffectSizeDistributionInfo &DES_info = effect_size_distributions_[p_trait_index]; return DES_info.default_dominance_coeff_; } slim_effect_t DefaultHemizygousDominanceForTrait(slim_trait_index_t p_trait_index) const { - const EffectDistributionInfo &DES_info = effect_distributions_[p_trait_index]; + const EffectSizeDistributionInfo &DES_info = effect_size_distributions_[p_trait_index]; return DES_info.default_hemizygous_dominance_coeff_; } - slim_effect_t DrawEffectForTrait(slim_trait_index_t p_trait_index) const; // draw a selection coefficient from the DE for a trait + slim_effect_t DrawEffectSizeForTrait(slim_trait_index_t p_trait_index) const; // draw a selection coefficient from the DE for a trait // @@ -271,14 +271,14 @@ class MutationType : public EidosDictionaryUnretained virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; EidosValue_SP ExecuteMethod_defaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_defaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_effectDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_effectDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_drawEffectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_effectSizeDistributionTypeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_effectSizeDistributionParamsForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_drawEffectSizeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_loggedData(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_logMutationData(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setDefaultDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_setDefaultHemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); - EidosValue_SP ExecuteMethod_setEffectDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_setEffectSizeDistributionForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); // Accelerated property access; see class EidosObject for comments on this mechanism static EidosValue *GetProperty_Accelerated_id(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index 428ca3cf..1e12fcbf 100644 --- a/core/slim_eidos_block.cpp +++ b/core/slim_eidos_block.cpp @@ -2078,7 +2078,7 @@ EidosTypeSpecifier SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(std: // with AddSignatureForProperty_TYPE_INTERPRETER(), a version of AddSignatureForProperty() that // uses scratch space belonging only to us, so we don't interfere with anything in SLiM itself. const std::string &trait_name = trait_name_token->token_string_; - const std::string &traitEffect_name = trait_name + "Effect"; + const std::string &traitEffectSize_name = trait_name + "EffectSize"; const std::string &traitDominance_name = trait_name + "Dominance"; const std::string &traitHemizygousDominance_name = trait_name + "HemizygousDominance"; const std::string &traitOffset_name = trait_name + "Offset"; @@ -2086,20 +2086,20 @@ EidosTypeSpecifier SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(std: EidosPropertySignature_CSP species_trait_signature((new EidosPropertySignature(trait_name, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP individual_trait_signature((new EidosPropertySignature(trait_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP individual_traitOffset_signature((new EidosPropertySignature(traitOffset_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); - EidosPropertySignature_CSP mutation_traitEffect_signature((new EidosPropertySignature(traitEffect_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP mutation_traitEffectSize_signature((new EidosPropertySignature(traitEffectSize_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP mutation_traitDominance_signature((new EidosPropertySignature(traitDominance_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP mutation_traitHemizygousDominance_signature((new EidosPropertySignature(traitHemizygousDominance_name, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); - EidosPropertySignature_CSP substitution_traitEffect_signature((new EidosPropertySignature(traitEffect_name, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); + EidosPropertySignature_CSP substitution_traitEffectSize_signature((new EidosPropertySignature(traitEffectSize_name, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP substitution_traitDominance_signature((new EidosPropertySignature(traitDominance_name, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); EidosPropertySignature_CSP substitution_traitHemizygousDominance_signature((new EidosPropertySignature(traitHemizygousDominance_name, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->MarkAsDynamicWithOwner("Trait")); gSLiM_Species_Class->AddSignatureForProperty_TYPE_INTERPRETER(species_trait_signature); gSLiM_Individual_Class->AddSignatureForProperty_TYPE_INTERPRETER(individual_trait_signature); gSLiM_Individual_Class->AddSignatureForProperty_TYPE_INTERPRETER(individual_traitOffset_signature); - gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitEffect_signature); + gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitEffectSize_signature); gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitDominance_signature); gSLiM_Mutation_Class->AddSignatureForProperty_TYPE_INTERPRETER(mutation_traitHemizygousDominance_signature); - gSLiM_Substitution_Class->AddSignatureForProperty_TYPE_INTERPRETER(substitution_traitEffect_signature); + gSLiM_Substitution_Class->AddSignatureForProperty_TYPE_INTERPRETER(substitution_traitEffectSize_signature); gSLiM_Substitution_Class->AddSignatureForProperty_TYPE_INTERPRETER(substitution_traitDominance_signature); gSLiM_Substitution_Class->AddSignatureForProperty_TYPE_INTERPRETER(substitution_traitHemizygousDominance_signature); } diff --git a/core/slim_functions.cpp b/core/slim_functions.cpp index d18f4b1d..6c22171e 100644 --- a/core/slim_functions.cpp +++ b/core/slim_functions.cpp @@ -705,7 +705,7 @@ R"V0G0N({ else muts = species.subsetMutations(mutType=mutType, chromosome=chromosome); - muts = muts[muts.effect < 0.0]; + muts = muts[muts.effectSize < 0.0]; // get frequencies and focus on those that are in the haplosomes q = haplosomes.mutationFrequenciesInHaplosomes(muts); @@ -716,7 +716,7 @@ R"V0G0N({ // fetch selection coefficients; note that we use the negation of // SLiM's selection coefficient, following Morton et al. 1956's usage - s = -muts.effect; + s = -muts.effectSize; // replace s > 1.0 with s == 1.0; a mutation can't be more lethal // than lethal (this can happen when drawing from a gamma distribution) diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 17b8efc1..1bed3ac2 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1280,8 +1280,8 @@ const std::string &gStr_subpopID = EidosRegisteredString("subpopID", gID_subpopI const std::string &gStr_convertToSubstitution = EidosRegisteredString("convertToSubstitution", gID_convertToSubstitution); const std::string &gStr_defaultDominanceForTrait = EidosRegisteredString("defaultDominanceForTrait", gID_defaultDominanceForTrait); const std::string &gStr_defaultHemizygousDominanceForTrait = EidosRegisteredString("defaultHemizygousDominanceForTrait", gID_defaultHemizygousDominanceForTrait); -const std::string &gStr_effectDistributionTypeForTrait = EidosRegisteredString("effectDistributionTypeForTrait", gID_effectDistributionTypeForTrait); -const std::string &gStr_effectDistributionParamsForTrait = EidosRegisteredString("effectDistributionParamsForTrait", gID_effectDistributionParamsForTrait); +const std::string &gStr_effectSizeDistributionTypeForTrait = EidosRegisteredString("effectSizeDistributionTypeForTrait", gID_effectSizeDistributionTypeForTrait); +const std::string &gStr_effectSizeDistributionParamsForTrait = EidosRegisteredString("effectSizeDistributionParamsForTrait", gID_effectSizeDistributionParamsForTrait); const std::string &gStr_dominance = EidosRegisteredString("dominance", gID_dominance); const std::string &gStr_hemizygousDominance = EidosRegisteredString("hemizygousDominance", gID_hemizygousDominance); const std::string &gStr_mutationStackGroup = EidosRegisteredString("mutationStackGroup", gID_mutationStackGroup); @@ -1408,20 +1408,20 @@ const std::string &gStr_removeMutations = EidosRegisteredString("removeMutations const std::string &gStr_setGenomicElementType = EidosRegisteredString("setGenomicElementType", gID_setGenomicElementType); const std::string &gStr_setMutationFractions = EidosRegisteredString("setMutationFractions", gID_setMutationFractions); const std::string &gStr_setMutationMatrix = EidosRegisteredString("setMutationMatrix", gID_setMutationMatrix); -const std::string &gStr_effectForTrait = EidosRegisteredString("effectForTrait", gID_effectForTrait); +const std::string &gStr_effectSizeForTrait = EidosRegisteredString("effectSizeForTrait", gID_effectSizeForTrait); const std::string &gStr_dominanceForTrait = EidosRegisteredString("dominanceForTrait", gID_dominanceForTrait); const std::string &gStr_hemizygousDominanceForTrait = EidosRegisteredString("hemizygousDominanceForTrait", gID_hemizygousDominanceForTrait); const std::string &gStr_isIndependentDominanceForTrait = EidosRegisteredString("isIndependentDominanceForTrait", gID_isIndependentDominanceForTrait); -const std::string &gStr_setEffectForTrait = EidosRegisteredString("setEffectForTrait", gID_setEffectForTrait); +const std::string &gStr_setEffectSizeForTrait = EidosRegisteredString("setEffectSizeForTrait", gID_setEffectSizeForTrait); const std::string &gStr_setDominanceForTrait = EidosRegisteredString("setDominanceForTrait", gID_setDominanceForTrait); const std::string &gStr_setHemizygousDominanceForTrait = EidosRegisteredString("setHemizygousDominanceForTrait", gID_setHemizygousDominanceForTrait); const std::string &gStr_setMutationType = EidosRegisteredString("setMutationType", gID_setMutationType); -const std::string &gStr_drawEffectForTrait = EidosRegisteredString("drawEffectForTrait", gID_drawEffectForTrait); +const std::string &gStr_drawEffectSizeForTrait = EidosRegisteredString("drawEffectSizeForTrait", gID_drawEffectSizeForTrait); const std::string &gStr_loggedData = EidosRegisteredString("loggedData", gID_loggedData); const std::string &gStr_logMutationData = EidosRegisteredString("logMutationData", gID_logMutationData); const std::string &gStr_setDefaultDominanceForTrait = EidosRegisteredString("setDefaultDominanceForTrait", gID_setDefaultDominanceForTrait); const std::string &gStr_setDefaultHemizygousDominanceForTrait = EidosRegisteredString("setDefaultHemizygousDominanceForTrait", gID_setDefaultHemizygousDominanceForTrait); -const std::string &gStr_setEffectDistributionForTrait = EidosRegisteredString("setEffectDistributionForTrait", gID_setEffectDistributionForTrait); +const std::string &gStr_setEffectSizeDistributionForTrait = EidosRegisteredString("setEffectSizeDistributionForTrait", gID_setEffectSizeDistributionForTrait); const std::string &gStr_addPatternForClone = EidosRegisteredString("addPatternForClone", gID_addPatternForClone); const std::string &gStr_addPatternForCross = EidosRegisteredString("addPatternForCross", gID_addPatternForCross); const std::string &gStr_addPatternForNull = EidosRegisteredString("addPatternForNull", gID_addPatternForNull); @@ -1574,6 +1574,7 @@ const std::string &gStr_parent2 = EidosRegisteredString("parent2", gID_parent2); const std::string &gStr_mut = EidosRegisteredString("mut", gID_mut); const std::string &gStr_trait = EidosRegisteredString("trait", gID_trait); const std::string &gStr_effect = EidosRegisteredString("effect", gID_effect); +const std::string &gStr_effectSize = EidosRegisteredString("effectSize", gID_effectSize); const std::string &gStr_homozygous = EidosRegisteredString("homozygous", gID_homozygous); const std::string &gStr_breakpoints = EidosRegisteredString("breakpoints", gID_breakpoints); const std::string &gStr_receiver = EidosRegisteredString("receiver", gID_receiver); diff --git a/core/slim_globals.h b/core/slim_globals.h index cfe43041..067c0c9f 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -126,7 +126,7 @@ typedef int64_t slim_haplosomeid_t; // identifiers for pedigreed haplosomes; no typedef int32_t slim_polymorphismid_t; // identifiers for polymorphisms, which need only 32 bits since they are only segregating mutations typedef int32_t slim_trait_index_t; // indices for traits; we are limited to 256 traits by SLIM_MAX_TRAITS at present, so this is plenty of room typedef uint32_t slim_operation_id_t; // used for MutationRun's operation_id_, as a unique identifier of a given task being worked upon -typedef float slim_effect_t; // storage of trait effects (e.g., selection coefficients) in memory-tight classes; also dominance coefficients +typedef float slim_effect_t; // storage of mutation effect sizes, trait effects, and dominance coefficients in memory-tight classes typedef float slim_fitness_t; // storage of fitness effects (e.g., fitnessScaling values) and final individual fitness values #define SLIM_MAX_TICK (1000000000L) // ticks range from 0 (init time) to this; SLIM_MAX_TICK + 1 is an "infinite" marker value @@ -864,8 +864,8 @@ extern const std::string &gStr_subpopID; extern const std::string &gStr_convertToSubstitution; extern const std::string &gStr_defaultDominanceForTrait; extern const std::string &gStr_defaultHemizygousDominanceForTrait; -extern const std::string &gStr_effectDistributionTypeForTrait; -extern const std::string &gStr_effectDistributionParamsForTrait; +extern const std::string &gStr_effectSizeDistributionTypeForTrait; +extern const std::string &gStr_effectSizeDistributionParamsForTrait; extern const std::string &gStr_dominance; extern const std::string &gStr_hemizygousDominance; extern const std::string &gStr_mutationStackGroup; @@ -991,20 +991,20 @@ extern const std::string &gStr_removeMutations; extern const std::string &gStr_setGenomicElementType; extern const std::string &gStr_setMutationFractions; extern const std::string &gStr_setMutationMatrix; -extern const std::string &gStr_effectForTrait; +extern const std::string &gStr_effectSizeForTrait; extern const std::string &gStr_dominanceForTrait; extern const std::string &gStr_hemizygousDominanceForTrait; extern const std::string &gStr_isIndependentDominanceForTrait; -extern const std::string &gStr_setEffectForTrait; +extern const std::string &gStr_setEffectSizeForTrait; extern const std::string &gStr_setDominanceForTrait; extern const std::string &gStr_setHemizygousDominanceForTrait; extern const std::string &gStr_setMutationType; -extern const std::string &gStr_drawEffectForTrait; +extern const std::string &gStr_drawEffectSizeForTrait; extern const std::string &gStr_loggedData; extern const std::string &gStr_logMutationData; extern const std::string &gStr_setDefaultDominanceForTrait; extern const std::string &gStr_setDefaultHemizygousDominanceForTrait; -extern const std::string &gStr_setEffectDistributionForTrait; +extern const std::string &gStr_setEffectSizeDistributionForTrait; extern const std::string &gStr_addPatternForClone; extern const std::string &gStr_addPatternForCross; extern const std::string &gStr_addPatternForNull; @@ -1156,6 +1156,7 @@ extern const std::string &gStr_parent2; extern const std::string &gStr_mut; extern const std::string &gStr_trait; extern const std::string &gStr_effect; +extern const std::string &gStr_effectSize; extern const std::string &gStr_homozygous; extern const std::string &gStr_breakpoints; extern const std::string &gStr_receiver; @@ -1358,8 +1359,8 @@ enum _SLiMGlobalStringID : int { gID_convertToSubstitution, gID_defaultDominanceForTrait, gID_defaultHemizygousDominanceForTrait, - gID_effectDistributionTypeForTrait, - gID_effectDistributionParamsForTrait, + gID_effectSizeDistributionTypeForTrait, + gID_effectSizeDistributionParamsForTrait, gID_dominance, gID_hemizygousDominance, gID_mutationStackGroup, @@ -1485,20 +1486,20 @@ enum _SLiMGlobalStringID : int { gID_setGenomicElementType, gID_setMutationFractions, gID_setMutationMatrix, - gID_effectForTrait, + gID_effectSizeForTrait, gID_dominanceForTrait, gID_hemizygousDominanceForTrait, gID_isIndependentDominanceForTrait, - gID_setEffectForTrait, + gID_setEffectSizeForTrait, gID_setDominanceForTrait, gID_setHemizygousDominanceForTrait, gID_setMutationType, - gID_drawEffectForTrait, + gID_drawEffectSizeForTrait, gID_loggedData, gID_logMutationData, gID_setDefaultDominanceForTrait, gID_setDefaultHemizygousDominanceForTrait, - gID_setEffectDistributionForTrait, + gID_setEffectSizeDistributionForTrait, gID_addPatternForClone, gID_addPatternForCross, gID_addPatternForNull, @@ -1650,6 +1651,7 @@ enum _SLiMGlobalStringID : int { gID_mut, gID_trait, gID_effect, + gID_effectSize, gID_homozygous, gID_breakpoints, gID_receiver, diff --git a/core/slim_test.cpp b/core/slim_test.cpp index aa99873a..c8010357 100644 --- a/core/slim_test.cpp +++ b/core/slim_test.cpp @@ -361,7 +361,7 @@ std::string gen1_setup("initialize() { initializeMutationRate(1e-7); initializeM std::string gen1_setup_sex("initialize() { initializeSex('X'); initializeMutationRate(1e-7); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } "); std::string gen2_stop(" 2 early() { stop(); } "); std::string gen1_setup_highmut_p1("initialize() { initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } "); -std::string gen1_setup_fixmut_p1("initialize() { initializeMutationRate(1e-4); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 5); } 10 early() { sim.mutations[0].setEffectForTrait(0, 500.0); sim.recalculateFitness(); } "); +std::string gen1_setup_fixmut_p1("initialize() { initializeMutationRate(1e-4); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 5); } 10 early() { sim.mutations[0].setEffectSizeForTrait(0, 500.0); sim.recalculateFitness(); } "); std::string gen1_setup_i1("initialize() { initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeInteractionType('i1', ''); } 1 early() { sim.addSubpop('p1', 10); } 1:10 late() { } "); std::string gen1_setup_i1x("initialize() { initializeSLiMOptions(dimensionality='x'); initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeInteractionType('i1', 'x'); } 1 early() { sim.addSubpop('p1', 10); } 1:10 late() { p1.individuals.x = runif(10); } "); std::string gen1_setup_i1xPx("initialize() { initializeSLiMOptions(dimensionality='x', periodicity='x'); initializeMutationRate(1e-5); initializeMutationType('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeInteractionType('i1', 'x'); } 1 early() { sim.addSubpop('p1', 10); } 1:10 late() { p1.individuals.x = runif(10); } "); diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 3079e62f..bebea1a0 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -39,8 +39,8 @@ void _RunMutationTypeTests(void) SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.convertToSubstitution == T) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.mutationStackGroup == 1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.mutationStackPolicy == 's') stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.effectDistributionParamsForTrait() == 0.0) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.effectDistributionTypeForTrait() == 'f') stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.effectSizeDistributionParamsForTrait() == 0.0) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.effectSizeDistributionTypeForTrait() == 'f') stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.defaultDominanceForTrait() == 0.5) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.defaultHemizygousDominanceForTrait() == 1.0) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup + "1 early() { if (m1.id == 1) stop(); }", __LINE__); @@ -74,78 +74,78 @@ void _RunMutationTypeTests(void) SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultHemizygousDominanceForTrait(NULL, 0.3); }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultHemizygousDominanceForTrait(0, 0.3); }", __LINE__); SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setDefaultHemizygousDominanceForTrait(sim.traits, 0.3); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (m1.effectDistributionTypeForTrait() == 'f' & m1.effectDistributionParamsForTrait() == 2.2) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'g' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3); if (m1.effectDistributionTypeForTrait() == 'e' & m1.effectDistributionParamsForTrait() == -3) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'n' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'p' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'w' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'return 1;'); if (m1.effectDistributionTypeForTrait() == 's' & identical(m1.effectDistributionParamsForTrait(), 'return 1;')) stop(); }", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'x', 1.5); stop(); }", "must be 'f', 'g', 'e', 'n', 'w', or 's'", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 3); stop(); }", "must be of type string", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 3.1); stop(); }", "must be of type string", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', T, 7.5); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, T); stop(); }", "must be of type numeric", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', T); stop(); }", "must be of type string", __LINE__); - - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, -1.0); }", "must have a shape parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, -1.0); }", "must have a standard deviation parameter >= 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 0.0); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, -1.0); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 0.0, 7.5); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', -1.0, 7.5); }", "must have a scale parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); - SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, -7.5); }", "must have a shape parameter > 0", __LINE__); - - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'return foo;'); } 100 early() { stop(); }", "undefined identifier foo", __LINE__, false); - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'x >< 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DES callback script", __LINE__, false); - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'x $ 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DES callback script", __LINE__, false); - - // Test MutationType - (float)drawEffectForTrait([integer$ n = 1]) + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'f', 2.2); if (m1.effectSizeDistributionTypeForTrait() == 'f' & m1.effectSizeDistributionParamsForTrait() == 2.2) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', 3.1, 7.5); if (m1.effectSizeDistributionTypeForTrait() == 'g' & identical(m1.effectSizeDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'e', -3); if (m1.effectSizeDistributionTypeForTrait() == 'e' & m1.effectSizeDistributionParamsForTrait() == -3) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', 3.1, 7.5); if (m1.effectSizeDistributionTypeForTrait() == 'n' & identical(m1.effectSizeDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', 3.1, 7.5); if (m1.effectSizeDistributionTypeForTrait() == 'p' & identical(m1.effectSizeDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 3.1, 7.5); if (m1.effectSizeDistributionTypeForTrait() == 'w' & identical(m1.effectSizeDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 's', 'return 1;'); if (m1.effectSizeDistributionTypeForTrait() == 's' & identical(m1.effectSizeDistributionParamsForTrait(), 'return 1;')) stop(); }", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'x', 1.5); stop(); }", "must be 'f', 'g', 'e', 'n', 'w', or 's'", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'f', 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'e', 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 's', 3); stop(); }", "must be of type string", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'f', '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'e', '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 's', 3.1); stop(); }", "must be of type string", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'f', T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'e', T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 's', T); stop(); }", "must be of type string", __LINE__); + + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', 3.1, -1.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', 3.1, -1.0); }", "must have a standard deviation parameter >= 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', 3.1, 0.0); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', 3.1, -1.0); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 0.0, 7.5); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', -1.0, 7.5); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 3.1, -7.5); }", "must have a shape parameter > 0", __LINE__); + + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 's', 'return foo;'); } 100 early() { stop(); }", "undefined identifier foo", __LINE__, false); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 's', 'x >< 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DES callback script", __LINE__, false); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 's', 'x $ 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DES callback script", __LINE__, false); + + // Test MutationType - (float)drawEffectSizeForTrait([integer$ n = 1]) // the parameters here are chosen so that these tests should fail extremely rarely - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (abs(m1.drawEffectForTrait() - 2.2) < 1e-6) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (all(abs(m1.drawEffectForTrait(NULL, 10) - rep(2.2, 10)) < 1e-6)) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 3.1) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3.0); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3.0); if (abs(mean(m1.drawEffectForTrait(NULL, 30000)) + 3.0) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 0.5); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 0.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 0.01); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 2.910106) < 0.1) stop(); }", __LINE__); - SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); m1.drawEffectForTrait(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 2.0) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'f', 2.2); if (abs(m1.drawEffectSizeForTrait() - 2.2) < 1e-6) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'f', 2.2); if (all(abs(m1.drawEffectSizeForTrait(NULL, 10) - rep(2.2, 10)) < 1e-6)) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', 3.1, 7.5); m1.drawEffectSizeForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'g', 3.1, 7.5); if (abs(mean(m1.drawEffectSizeForTrait(NULL, 5000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'e', -3.0); m1.drawEffectSizeForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'e', -3.0); if (abs(mean(m1.drawEffectSizeForTrait(NULL, 30000)) + 3.0) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', 3.1, 0.5); m1.drawEffectSizeForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'n', 3.1, 0.5); if (abs(mean(m1.drawEffectSizeForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', 3.1, 7.5); m1.drawEffectSizeForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'p', 3.1, 0.01); if (abs(mean(m1.drawEffectSizeForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 3.1, 7.5); m1.drawEffectSizeForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 'w', 3.1, 7.5); if (abs(mean(m1.drawEffectSizeForTrait(NULL, 2000)) - 2.910106) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); m1.drawEffectSizeForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectSizeDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); if (abs(mean(m1.drawEffectSizeForTrait(NULL, 5000)) - 2.0) < 0.1) stop(); }", __LINE__); // test mutation data recording with logMutationData() and loggedData() std::string data_recording = @@ -701,12 +701,12 @@ void _RunMutationTests(void) SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if (mut.mutationType == m1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if ((mut.originTick >= 1) & (mut.originTick < 10)) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if ((mut.position >= 0) & (mut.position < 100000)) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if (mut.effect == 0.0) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if (mut.effectSize == 0.0) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; if (mut.subpopID == 1) stop(); }", __LINE__); SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.mutationType = m1; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.originTick = 1; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.position = 0; stop(); }", "read-only property", __LINE__); - SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.effect = 0.1; stop(); }", "read-only property", __LINE__); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.effectSize = 0.1; stop(); }", "read-only property", __LINE__); SLiMAssertScriptStop(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.subpopID = 237; if (mut.subpopID == 237) stop(); }", __LINE__); // legal; this field may be used as a user tag SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; mut.tag; }", "before being set", __LINE__); SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "10 early() { mut = sim.mutations[0]; c(mut,mut).tag; }", "before being set", __LINE__); @@ -732,13 +732,13 @@ void _RunSubstitutionTests(void) SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; if (sub.mutationType == m1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; if (sub.originTick > 0 & sub.originTick <= 10) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; if (sub.position >= 0 & sub.position <= 99999) stop(); }", __LINE__); - SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { if (sum(sim.substitutions.effect == 500.0) == 1) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { if (sum(sim.substitutions.effectSize == 500.0) == 1) stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; if (sub.subpopID == 1) stop(); }", __LINE__); SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.fixationTick = 10; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.mutationType = m1; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.originTick = 10; stop(); }", "read-only property", __LINE__); SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.position = 99999; stop(); }", "read-only property", __LINE__); - SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.effect = 50.0; stop(); }", "read-only property", __LINE__); + SLiMAssertScriptRaise(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.effectSize = 50.0; stop(); }", "read-only property", __LINE__); SLiMAssertScriptStop(gen1_setup_fixmut_p1 + "50 early() { sub = sim.substitutions[0]; sub.subpopID = 237; if (sub.subpopID == 237) stop(); }", __LINE__); // legal; this field may be used as a user tag } @@ -787,7 +787,7 @@ void _RunHaplosomeTests(const std::string &temp_path) SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewDrawnMutation(1, 5000, 237); stop(); }", __LINE__); // bad subpop, but this is legal to allow "tagging" of mutations SLiMAssertScriptRaise(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewDrawnMutation(1, 5000, -1); stop(); }", "out of range", __LINE__); // however, such tags must be within range - // Test Haplosome + (object)addNewMutation(io mutationType, numeric effect, integer position, [Nio originSubpop]) + // Test Haplosome + (object)addNewMutation(io mutationType, numeric effectSize, integer position, [Nio originSubpop]) SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewMutation(m1, 0.1, 5000, p1); p1.haplosomes.addMutations(mut); stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewMutation(m1, 0.1, 5000, 1); p1.haplosomes.addMutations(mut); stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewMutation(m1, 0.1, 5000); p1.haplosomes.addMutations(mut); stop(); }", __LINE__); @@ -827,7 +827,7 @@ void _RunHaplosomeTests(const std::string &temp_path) SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewDrawnMutation(1, 5000, 237); stop(); }", __LINE__); // bad subpop, but this is legal to allow "tagging" of mutations SLiMAssertScriptRaise(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewDrawnMutation(1, 5000, -1); stop(); }", "out of range", __LINE__); // however, such tags must be within range - // Test Haplosome + (object)addNewMutation(io mutationType, numeric effect, integer position, [io originSubpop]) with new class method non-multiplex behavior + // Test Haplosome + (object)addNewMutation(io mutationType, numeric effectSize, integer position, [io originSubpop]) with new class method non-multiplex behavior SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewMutation(m1, 0.1, 5000, p1); stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewMutation(m1, 0.1, 5000, 1); stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewMutation(m1, 0.1, 5000); stop(); }", __LINE__); @@ -1170,24 +1170,24 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.heightOffset = INF; }", "required to be a finite value (not INF or NAN)", __LINE__); SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.heightOffset = c(1,INF,3,INF,5); }", "required to be a finite value (not INF or NAN)", __LINE__); - // Mutation effectForTrait() - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectForTrait(0), 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectForTrait(1), 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectForTrait(NULL), c(0.0, 0.0))) stop(); }"); + // Mutation effectSizeForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectSizeForTrait(0), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectSizeForTrait(1), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.effectSizeForTrait(NULL), c(0.0, 0.0))) stop(); }"); - // Mutation setEffectForTrait() - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, 3); mut.setEffectForTrait(1, 4.5); if (!identical(mut.effectForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, 1:5 * 2 - 1); mut.setEffectForTrait(1, 1:5 * 2); if (!identical(mut.effectForTrait(NULL), 1.0:10)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(NULL, 1:10); if (!identical(mut.effectForTrait(NULL), 1.0:10)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(NULL, 1:10 + 0.5); if (!identical(mut.effectForTrait(NULL), 1:10 + 0.5)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(0,1), 1:10); if (!identical(mut.effectForTrait(NULL), 1.0:10)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(0,1), 1:10 + 0.5); if (!identical(mut.effectForTrait(NULL), 1:10 + 0.5)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(1,0), 1:10); if (!identical(mut.effectForTrait(c(1,0)), 1.0:10)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.effectForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); - SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, NAN); }", "non-finite after setEffectForTrait()", __LINE__); - SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, c(1,NAN,3,NAN,5)); }", "non-finite after setEffectForTrait()", __LINE__); - SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, INF); }", "non-finite after setEffectForTrait()", __LINE__); - SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectForTrait(0, c(1,INF,3,INF,5)); }", "non-finite after setEffectForTrait()", __LINE__); + // Mutation setEffectSizeForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(0, 3); mut.setEffectSizeForTrait(1, 4.5); if (!identical(mut.effectSizeForTrait(NULL), rep(c(3, 4.5), 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(0, 1:5 * 2 - 1); mut.setEffectSizeForTrait(1, 1:5 * 2); if (!identical(mut.effectSizeForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(NULL, 1:10); if (!identical(mut.effectSizeForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(NULL, 1:10 + 0.5); if (!identical(mut.effectSizeForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(c(0,1), 1:10); if (!identical(mut.effectSizeForTrait(NULL), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(c(0,1), 1:10 + 0.5); if (!identical(mut.effectSizeForTrait(NULL), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(c(1,0), 1:10); if (!identical(mut.effectSizeForTrait(c(1,0)), 1.0:10)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(c(1,0), 1:10 + 0.5); if (!identical(mut.effectSizeForTrait(c(1,0)), 1:10 + 0.5)) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(0, NAN); }", "non-finite after setEffectSizeForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(0, c(1,NAN,3,NAN,5)); }", "non-finite after setEffectSizeForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(0, INF); }", "non-finite after setEffectSizeForTrait()", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setEffectSizeForTrait(0, c(1,INF,3,INF,5)); }", "non-finite after setEffectSizeForTrait()", __LINE__); // Mutation dominanceForTrait() SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.dominanceForTrait(0), 0.5)) stop(); }"); @@ -1257,10 +1257,10 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(0, INF); }", "non-finite after setDefaultHemizygousDominanceForTrait()", __LINE__); SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(NULL, c(0.25, INF)); }", "non-finite after setDefaultHemizygousDominanceForTrait()", __LINE__); - // Substitution effectForTrait() - SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(0), 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(1), 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectForTrait(NULL), c(0.0, 0.0))) stop(); }"); + // Substitution effectSizeForTrait() + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectSizeForTrait(0), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectSizeForTrait(1), 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.effectSizeForTrait(NULL), c(0.0, 0.0))) stop(); }"); // Substitution dominanceForTrait() SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.dominanceForTrait(0), 0.5)) stop(); }"); @@ -1272,13 +1272,13 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.hemizygousDominanceForTrait(1), 1.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.hemizygousDominanceForTrait(NULL), c(1.0, 1.0))) stop(); }"); - // Mutation Effect property - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightEffect, 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightEffect, 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffect = 0.25; if (!identical(mut.heightEffect, 0.25)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightEffect = 0.25; if (!identical(mut.weightEffect, 0.25)) stop(); }"); - SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffect = NAN; }", "required to be a finite value (not INF or NAN)", __LINE__); - SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffect = INF; }", "required to be a finite value (not INF or NAN)", __LINE__); + // Mutation EffectSize property + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightEffectSize, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.weightEffectSize, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffectSize = 0.25; if (!identical(mut.heightEffectSize, 0.25)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.weightEffectSize = 0.25; if (!identical(mut.weightEffectSize, 0.25)) stop(); }"); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffectSize = NAN; }", "required to be a finite value (not INF or NAN)", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightEffectSize = INF; }", "required to be a finite value (not INF or NAN)", __LINE__); // Mutation Dominance property SLiMAssertScriptSuccess(mt_base_p1 + "5 late() { mut = sim.mutations[0]; if (!identical(mut.heightDominance, 0.5)) stop(); }"); @@ -1297,9 +1297,9 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightHemizygousDominance = NAN; }", "required to be finite or NAN", __LINE__); SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0]; mut.heightHemizygousDominance = INF; }", "required to be finite or NAN", __LINE__); - // Substitution Effect property - SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.heightEffect, 0.0)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.weightEffect, 0.0)) stop(); }"); + // Substitution EffectSize property + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.heightEffectSize, 0.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.weightEffectSize, 0.0)) stop(); }"); // Substitution Dominance property SLiMAssertScriptSuccess(mt_base_p1 + "200 late() { sub = sim.substitutions[0]; if (!identical(sub.heightDominance, 0.5)) stop(); }"); @@ -1337,19 +1337,19 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.isNeutral == T)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.isIndependentDominanceForTrait(0) == F)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.dominance == 0.5)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.effect == 0.0)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0);" + middle + "if (all(muts.effectSize == 0.0)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.isNeutral == F)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.isIndependentDominanceForTrait(0) == F)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (all(muts.dominance == 0.5)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (allClose(muts.effect, 0.0001)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "if (allClose(muts.effectSize, 0.0001)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.isNeutral == T)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.isIndependentDominanceForTrait(0) == T)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.dominance == 0.5)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.effect == 0.0)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0);" + middle + "if (all(muts.effectSize == 0.0)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (all(muts.isNeutral == F)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (all(muts.isIndependentDominanceForTrait(0) == T)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (allClose(muts.dominance, 0.4999875)) stop(); }"); // h = (sqrt(1+s)-1)/s - SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (allClose(muts.effect, 0.0001)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (allClose(muts.effectSize, 0.0001)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.setDominanceForTrait(0, 0.5); if (all(muts.dominance == 0.5)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.heightDominance = 0.5; if (all(muts.dominance == 0.5)) stop(); }"); @@ -1427,7 +1427,7 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } } 1:10 early() { f1 = p1.individuals.cachedFitness; - f2 = sapply(p1.individuals, "muts = applyValue.haplosomes.mutations; product(1.0 + muts.effect * muts.dominance);"); + f2 = sapply(p1.individuals, "muts = applyValue.haplosomes.mutations; product(1.0 + muts.effectSize * muts.dominance);"); if (!allClose(f1, f2)) stop(); } @@ -1728,14 +1728,14 @@ initialize() { initializeMutationType("m1", 0.4, "f", 0.0); // neutral for all traits initializeMutationType("m2", 0.4, "e", 0.001); // beneficial for the popgen traits - m2.setEffectDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits - m2.setEffectDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits + m2.setEffectSizeDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits + m2.setEffectSizeDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits initializeMutationType("m3", 0.4, "g", -0.001, 1.0); // deleterious for the popgen traits - m3.setEffectDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits - m3.setEffectDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits + m3.setEffectSizeDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits + m3.setEffectSizeDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits - c(m2,m3).setEffectDistributionForTrait(n3T, "n", -5.0, 0.5); // very biased and wide DES for n3T + c(m2,m3).setEffectSizeDistributionForTrait(n3T, "n", -5.0, 0.5); // very biased and wide DES for n3T // set up independent dominance for popgen2T and quant2T; note that setting this for m1 should be unnecessary (it is neutral) c(m1,m2,m3).setDefaultDominanceForTrait(c(popgen2T, quant2T), NAN); diff --git a/core/slim_test_other.cpp b/core/slim_test_other.cpp index 781b604b..76ce4dd1 100644 --- a/core/slim_test_other.cpp +++ b/core/slim_test_other.cpp @@ -2736,7 +2736,7 @@ void _RunNucleotideMethodTests(void) // nucleotide & nucleotideValue std::string nuc_highmut("initialize() { initializeSLiMOptions(nucleotideBased=T); initializeAncestralNucleotides(randomNucleotides(1e2)); initializeMutationTypeNuc('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0, mmJukesCantor(1e-2)); initializeGenomicElement(g1, 0, 1e2-1); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } "); - std::string nuc_fixmut("initialize() { initializeSLiMOptions(nucleotideBased=T); initializeAncestralNucleotides(randomNucleotides(1e2)); initializeMutationTypeNuc('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0, mmJukesCantor(1e-2)); initializeGenomicElement(g1, 0, 1e2-1); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } 10 early() { sim.mutations[0].setEffectForTrait(0, 500.0); sim.recalculateFitness(); } "); + std::string nuc_fixmut("initialize() { initializeSLiMOptions(nucleotideBased=T); initializeAncestralNucleotides(randomNucleotides(1e2)); initializeMutationTypeNuc('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0, mmJukesCantor(1e-2)); initializeGenomicElement(g1, 0, 1e2-1); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } 10 early() { sim.mutations[0].setEffectSizeForTrait(0, 500.0); sim.recalculateFitness(); } "); SLiMAssertScriptStop(nuc_highmut + "10 early() { mut = sim.mutations[0]; mut.nucleotide; stop(); }", __LINE__); SLiMAssertScriptStop(nuc_highmut + "10 early() { mut = sim.mutations[0]; mut.nucleotideValue; stop(); }", __LINE__); diff --git a/core/species.cpp b/core/species.cpp index 5bcf65a4..3a0126c0 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -234,7 +234,7 @@ void Species::AutogenerationConfigurationChanged(void) // recalculate whether the mutation type's effect distributions are all neutral mutation_type_ptr->all_neutral_DES_ = true; - for (EffectDistributionInfo &DES_info : mutation_type_ptr->effect_distributions_) + for (EffectSizeDistributionInfo &DES_info : mutation_type_ptr->effect_size_distributions_) { if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) mutation_type_ptr->all_neutral_DES_ = false; @@ -280,7 +280,7 @@ void Species::AutogenerationConfigurationChanged(void) for (Trait *trait : traits_) { slim_trait_index_t trait_index = trait->Index(); - EffectDistributionInfo &DES_info = mutation_type_ptr->effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = mutation_type_ptr->effect_size_distributions_[trait_index]; if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) { @@ -310,7 +310,7 @@ void Species::CheckOptimizationFlags(void) { MutationType *mutation_type_ptr = mut_type_iter.second; - for (EffectDistributionInfo &DES_info : mutation_type_ptr->effect_distributions_) + for (EffectSizeDistributionInfo &DES_info : mutation_type_ptr->effect_size_distributions_) { if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) if (mutation_type_ptr->all_neutral_DES_ != false) @@ -344,7 +344,7 @@ void Species::CheckOptimizationFlags(void) for (Trait *trait : traits_) { slim_trait_index_t trait_index = trait->Index(); - EffectDistributionInfo &DES_info = mutation_type_ptr->effect_distributions_[trait_index]; + EffectSizeDistributionInfo &DES_info = mutation_type_ptr->effect_size_distributions_[trait_index]; if ((DES_info.DES_type_ != DESType::kFixed) || (DES_info.DES_parameters_[0] != 0.0)) { diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index e3083862..2239f6b1 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -648,7 +648,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: { // The default distribution is a fixed effect of 0.0, and ellipsis arguments may not be supplied if (p_arguments.size() != 3) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeMutationType): with distributionType of NULL, ellipsis arguments may not be supplied to " << p_function_name << "(); the distribution of effect sizes is already completely specified." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeMutationType): with distributionType of NULL, ellipsis arguments may not be supplied to " << p_function_name << "(); the distribution of effect size is already completely specified." << EidosTerminate(); DES_type = DESType::kFixed; DES_parameters.push_back(0.0); @@ -1671,8 +1671,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string if (trait->Name() == name) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name is unique within the species; there is already a trait in this species with the name '" << name << "'." << EidosTerminate(); - if (Eidos_string_hasSuffix(name, "Effect") || Eidos_string_hasSuffix(name, "Dominance") || Eidos_string_hasSuffix(name, "Hemizygous") || Eidos_string_hasSuffix(name, "Offset")) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name does not end in 'Effect', 'Dominance', 'Hemizygous', or 'Offset' to avoid naming conflicts and general confusion." << EidosTerminate(); + if (Eidos_string_hasSuffix(name, "EffectSize") || Eidos_string_hasSuffix(name, "Dominance") || Eidos_string_hasSuffix(name, "Hemizygous") || Eidos_string_hasSuffix(name, "Offset")) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name does not end in 'EffectSize', 'Dominance', 'Hemizygous', or 'Offset' to avoid naming conflicts and general confusion." << EidosTerminate(); if (name == "NULL") EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() does not allow a trait name of 'NULL', to avoid naming conflicts and general confusion." << EidosTerminate(); @@ -1756,7 +1756,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string // SLiMgui the traits from one model will show up in a different model running at the same time, and // registered trait properties will not go away when you recycle. I'm ok with that. EidosGlobalStringID trait_stringID = EidosStringRegistry::GlobalStringIDForString(name); - EidosGlobalStringID traitEffect_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Effect"); + EidosGlobalStringID traitEffectSize_stringID = EidosStringRegistry::GlobalStringIDForString(name + "EffectSize"); EidosGlobalStringID traitDominance_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Dominance"); EidosGlobalStringID traitHemizygousDominance_stringID = EidosStringRegistry::GlobalStringIDForString(name + "HemizygousDominance"); EidosGlobalStringID traitOffset_stringID = EidosStringRegistry::GlobalStringIDForString(name + "Offset"); @@ -1833,8 +1833,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } { - // add a Mutation Effect property that returns the effect size for the trait in a mutation - const EidosPropertySignature *existing_signature = gSLiM_Mutation_Class->SignatureForProperty(traitEffect_stringID); + // add a Mutation EffectSize property that returns the effect size for the trait in a mutation + const EidosPropertySignature *existing_signature = gSLiM_Mutation_Class->SignatureForProperty(traitEffectSize_stringID); if (existing_signature) { @@ -1846,7 +1846,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string else { // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this - EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "Effect", false, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "EffectSize", false, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> MarkAsDynamicWithOwner("Trait")); gSLiM_Mutation_Class->AddSignatureForProperty(signature); @@ -1896,8 +1896,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string } { - // add a Substitution Effect property that returns the effect size for the trait in a substitution - const EidosPropertySignature *existing_signature = gSLiM_Substitution_Class->SignatureForProperty(traitEffect_stringID); + // add a Substitution EffectSize property that returns the effect size for the trait in a substitution + const EidosPropertySignature *existing_signature = gSLiM_Substitution_Class->SignatureForProperty(traitEffectSize_stringID); if (existing_signature) { @@ -1909,7 +1909,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string else { // ALSO MAINTAIN: SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(), which also tracks this - EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "Effect", true, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> + EidosPropertySignature_CSP signature((new EidosPropertySignature(name + "EffectSize", true, kEidosValueMaskFloat | kEidosValueMaskSingleton))-> MarkAsDynamicWithOwner("Trait")); gSLiM_Substitution_Class->AddSignatureForProperty(signature); diff --git a/core/substitution.cpp b/core/substitution.cpp index 0407e97d..9cc97b9d 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -360,7 +360,7 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) return mutation_type_ptr_->SymbolTableEntry().second; case gID_position: // ACCELERATED return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(position_)); - case gID_effect: + case gID_effectSize: { // This is not accelerated, because it's a bit tricky; each substitution could belong to a different species, // and thus be associated with a different number of traits. It isn't expected that this will be a hot path. @@ -377,9 +377,9 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { - slim_effect_t effect = trait_info_[trait_index].effect_size_; + slim_effect_t effect_size = trait_info_[trait_index].effect_size_; - float_result->push_float_no_check((double)effect); + float_result->push_float_no_check((double)effect_size); } return EidosValue_SP(float_result); @@ -493,16 +493,16 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: - // Here we implement a special behavior: you can do substitution.Effect, substitution.Dominance, + // Here we implement a special behavior: you can do substitution.EffectSize, substitution.Dominance, // and substitution.HemizygousDominance to access a trait's values directly. // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). Species &species = mutation_type_ptr_->species_; const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); - if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) + if ((property_string.length() > 10) && Eidos_string_hasSuffix(property_string, "EffectSize")) { - std::string trait_name = property_string.substr(0, property_string.length() - 6); + std::string trait_name = property_string.substr(0, property_string.length() - 10); Trait *trait = species.TraitFromName(trait_name); if (trait) @@ -734,7 +734,7 @@ EidosValue_SP Substitution::ExecuteInstanceMethod(EidosGlobalStringID p_method_i { switch (p_method_id) { - case gID_effectForTrait: return ExecuteMethod_effectForTrait(p_method_id, p_arguments, p_interpreter); + case gID_effectSizeForTrait: return ExecuteMethod_effectSizeForTrait(p_method_id, p_arguments, p_interpreter); case gID_dominanceForTrait: return ExecuteMethod_dominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_hemizygousDominanceForTrait: return ExecuteMethod_hemizygousDominanceForTrait(p_method_id, p_arguments, p_interpreter); case gID_isIndependentDominanceForTrait: return ExecuteMethod_isIndependentDominanceForTrait(p_method_id, p_arguments, p_interpreter); @@ -742,9 +742,9 @@ EidosValue_SP Substitution::ExecuteInstanceMethod(EidosGlobalStringID p_method_i } } -// ********************* - (float)effectForTrait([Niso trait = NULL]) +// ********************* - (float)effectSizeForTrait([Niso trait = NULL]) // -EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +EidosValue_SP Substitution::ExecuteMethod_effectSizeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { #pragma unused (p_method_id, p_arguments, p_interpreter) EidosValue *trait_value = p_arguments[0].get(); @@ -752,14 +752,14 @@ EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_m // get the trait indices, with bounds-checking Species &species = mutation_type_ptr_->species_; std::vector trait_indices; - species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectForTrait"); + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectSizeForTrait"); if (trait_indices.size() == 1) { slim_trait_index_t trait_index = trait_indices[0]; - slim_effect_t effect = trait_info_[trait_index].effect_size_; + slim_effect_t effect_size = trait_info_[trait_index].effect_size_; - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)effect)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)effect_size)); } else { @@ -767,9 +767,9 @@ EidosValue_SP Substitution::ExecuteMethod_effectForTrait(EidosGlobalStringID p_m for (slim_trait_index_t trait_index : trait_indices) { - slim_effect_t effect = trait_info_[trait_index].effect_size_; + slim_effect_t effect_size = trait_info_[trait_index].effect_size_; - float_result->push_float_no_check((double)effect); + float_result->push_float_no_check((double)effect_size); } return EidosValue_SP(float_result); @@ -907,7 +907,7 @@ std::vector *Substitution_Class::Properties_MUTABLE( properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_isNeutral, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Mutation::GetProperty_Accelerated_isNeutral)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_mutationType, true, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_mutationType)); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_position, true, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_position)); - properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effect, true, kEidosValueMaskFloat))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_effectSize, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_dominance, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_hemizygousDominance, true, kEidosValueMaskFloat))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_subpopID, false, kEidosValueMaskInt | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_subpopID)); @@ -933,7 +933,7 @@ const std::vector *Substitution_Class::Methods(void) c methods = new std::vector(*super::Methods()); - methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectSizeForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_isIndependentDominanceForTrait, kEidosValueMaskLogical))->AddIntStringObject_ON(gStr_trait, gSLiM_Trait_Class, gStaticEidosValueNULL)); diff --git a/core/substitution.h b/core/substitution.h index bf4b84e8..4ce33490 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -114,7 +114,7 @@ class Substitution : public EidosDictionaryRetained virtual EidosValue_SP GetProperty(EidosGlobalStringID p_property_id) override; virtual void SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_value) override; virtual EidosValue_SP ExecuteInstanceMethod(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) override; - EidosValue_SP ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_effectSizeForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_isIndependentDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); diff --git a/eidos/eidos_globals.h b/eidos/eidos_globals.h index ef179d3d..3db32270 100644 --- a/eidos/eidos_globals.h +++ b/eidos/eidos_globals.h @@ -1370,7 +1370,7 @@ enum _EidosGlobalStringID : uint32_t gEidosID_Individual, gEidosID_LastEntry, // IDs added by the Context should start here - gEidosID_LastContextEntry = 570 // IDs added by the Context must end before this value; Eidos reserves the remaining values + gEidosID_LastContextEntry = 580 // IDs added by the Context must end before this value; Eidos reserves the remaining values }; extern std::vector gEidosConstantNames; // T, F, NULL, PI, E, INF, NAN From a7cfc1ddcd66da9dee7ee2442df3f7d0126532cb Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 21 Jan 2026 23:52:35 -0600 Subject: [PATCH 089/107] change dominanceCoeff to defaultDominance, in initializeMutationType() --- QtSLiM/QtSLiMWindow.cpp | 8 ++++++++ SLiMgui/SLiMHelpFunctions.rtf | 9 +++++---- VERSIONS | 1 + core/community_eidos.cpp | 4 ++-- core/mutation_type.cpp | 8 ++++---- core/mutation_type.h | 4 ++-- core/slim_test_core.cpp | 2 +- core/slim_test_genetics.cpp | 2 +- core/species_eidos.cpp | 18 +++++++++--------- 9 files changed, 33 insertions(+), 23 deletions(-) diff --git a/QtSLiM/QtSLiMWindow.cpp b/QtSLiM/QtSLiMWindow.cpp index 816a5dff..4453b223 100644 --- a/QtSLiM/QtSLiMWindow.cpp +++ b/QtSLiM/QtSLiMWindow.cpp @@ -2146,6 +2146,14 @@ bool QtSLiMWindow::checkTerminationForAutofix(QString terminationMessage) // the above autofix is imperfect; if the user is assigning into hemizygousDominanceCoeff, it really needs to be corrected to be a call to setDefaultHemizygousDominanceForTrait() + if (terminationMessage.contains("unrecognized named argument 'dominanceCoeff' to initializeMutationType()") && + (selectionString == "dominanceCoeff")) + return offerAndExecuteAutofix(selection, "defaultDominance", "The `dominanceCoeff` parameter to initializeMutationType() has been renamed to `defaultDominance`.", terminationMessage); + + if (terminationMessage.contains("unrecognized named argument 'dominanceCoeff' to initializeMutationTypeNuc()") && + (selectionString == "dominanceCoeff")) + return offerAndExecuteAutofix(selection, "defaultDominance", "The `dominanceCoeff` parameter to initializeMutationTypeNuc() has been renamed to `defaultDominance`.", terminationMessage); + if (terminationMessage.contains("property distributionType is not defined for object element type MutationType") && (selectionString == "distributionType")) return offerAndExecuteAutofix(selection, "effectSizeDistributionTypeForTrait()", "The `distributionType` property of MutationType has become the method `effectSizeDistributionTypeForTrait()`.", terminationMessage); diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 7cd26fd7..6d1b4c1d 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -870,7 +870,7 @@ This function is written in Eidos, and its source code can be viewed with \f2\fs20 , so you can copy and modify its code if you need to modify its functionality.\ \pard\pardeftab543\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf0 (object$)initializeMutationType(is$\'a0id, numeric$\'a0dominanceCoeff, \cf2 [Ns$\'a0distributionType\'a0=\'a0NULL]\cf0 , ...) +\f1\fs18 \cf0 (object$)initializeMutationType(is$\'a0id, numeric$\'a0\cf2 defaultDominance\cf0 , \cf2 [Ns$\'a0distributionType\'a0=\'a0NULL]\cf0 , ...) \f4 \ \pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 @@ -888,7 +888,7 @@ This function is written in Eidos, and its source code can be viewed with \f1\fs18 m5 \f2\fs20 , is immediately available; the return value also provides the new object.\ The -\f1\fs18 dominanceCoeff +\f1\fs18 defaultDominance \f2\fs20 parameter supplies the default dominance coefficient for the mutation type, for all traits; \f1\fs18 0.0 \f2\fs20 produces no dominance, @@ -896,7 +896,7 @@ The \f2\fs20 complete dominance, and values greater than \f1\fs18 1.0 \f2\fs20 , overdominance. A -\f1\fs18 dominanceCoeff +\f1\fs18 defaultDominance \f2\fs20 value of \f1\fs18 NAN \f2\fs20 configures the mutation type to use \'93independent dominance\'94 for new mutations of that type, for all traits; see the class @@ -989,7 +989,8 @@ Note that by default in WF models, all mutations of a given mutation type will b \f2\fs20 .\ \pard\pardeftab543\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf2 (object$)initializeMutationTypeNuc(is$\'a0id, numeric$\'a0dominanceCoeff, \kerning1\expnd0\expndtw0 [Ns$\'a0distributionType\'a0=\'a0NULL]\expnd0\expndtw0\kerning0 +\f1\fs18 \cf2 (object$)initializeMutationTypeNuc(is$\'a0id, numeric$\'a0\kerning1\expnd0\expndtw0 defaultDominance\expnd0\expndtw0\kerning0 +, \kerning1\expnd0\expndtw0 [Ns$\'a0distributionType\'a0=\'a0NULL]\expnd0\expndtw0\kerning0 , ...)\ \pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 diff --git a/VERSIONS b/VERSIONS index 362eb783..d9d1868e 100644 --- a/VERSIONS +++ b/VERSIONS @@ -163,6 +163,7 @@ multitrait branch: addNewMutation() parameter "effect" -> "effectSize" setEffectSizeForTrait() parameter "effect" -> "effectSize" MutationType's loggedData() parameter "effect" -> "effectSize" and lots of related fallout for loggedData() and logMutationData(), logged_effect_, running_effect_, etc. + for initializeMutationType() and initializeMutationTypeNuc(), change the "dominanceCoeff" parameter to "defaultDominance" version 5.1 (Eidos version 4.1): diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index 8603f426..ea7ad311 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -120,9 +120,9 @@ const std::vector *Community::ZeroTickFunctionSignat sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeInteractionType, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_InteractionType_Class, "SLiM")) ->AddIntString_S("id")->AddString_S(gStr_spatiality)->AddLogical_OS(gStr_reciprocal, gStaticEidosValue_LogicalF)->AddNumeric_OS(gStr_maxDistance, gStaticEidosValue_FloatINF)->AddString_OS(gStr_sexSegregation, gStaticEidosValue_StringDoubleAsterisk)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeMutationType, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class, "SLiM")) - ->AddIntString_S("id")->AddNumeric_S("dominanceCoeff")->AddString_OSN("distributionType", gStaticEidosValueNULL)->AddEllipsis()); + ->AddIntString_S("id")->AddNumeric_S("defaultDominance")->AddString_OSN("distributionType", gStaticEidosValueNULL)->AddEllipsis()); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeMutationTypeNuc, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_MutationType_Class, "SLiM")) - ->AddIntString_S("id")->AddNumeric_S("dominanceCoeff")->AddString_OSN("distributionType", gStaticEidosValueNULL)->AddEllipsis()); + ->AddIntString_S("id")->AddNumeric_S("defaultDominance")->AddString_OSN("distributionType", gStaticEidosValueNULL)->AddEllipsis()); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeRecombinationRate, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric("rates")->AddInt_ON("ends", gStaticEidosValueNULL)->AddString_OS("sex", gStaticEidosValue_StringAsterisk)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeTrait, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class, "SLiM"))->AddString_S("name")->AddString_S("type")->AddFloat_OSN("baselineOffset", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetMean", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetSD", gStaticEidosValueNULL)->AddLogical_OS("directFitnessEffect", gStaticEidosValue_LogicalF)); diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 7b3d6c2f..18a43222 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -58,9 +58,9 @@ std::ostream& operator<<(std::ostream& p_out, DESType p_DES_type) #pragma mark - #ifdef SLIMGUI -MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings, int p_mutation_type_index) : +MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_default_dominance, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings, int p_mutation_type_index) : #else -MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings) : +MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_default_dominance, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings) : #endif self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStringWithPrefix('m', p_mutation_type_id)), EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(this, gSLiM_MutationType_Class))), species_(p_species), mutation_type_id_(p_mutation_type_id), nucleotide_based_(p_nuc_based), convert_to_substitution_(false), stack_policy_(MutationStackPolicy::kStack), stack_group_(p_mutation_type_id) @@ -81,7 +81,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr if ((p_DES_parameters.size() == 0) && (p_DES_strings.size() == 0)) EIDOS_TERMINATION << "ERROR (MutationType::MutationType): invalid mutation type parameters." << EidosTerminate(); // intentionally no bounds checks for DES parameters; the count of DES parameters is checked prior to construction - // intentionally no bounds check for dominance_coeff_ + // intentionally no bounds check for p_default_dominance // determine whether this mutation type has a neutral DES // note that we do not set species_all_neutral_mutations_ = false here; we wait until this muttype is used @@ -95,7 +95,7 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr // set up DE entries for all traits; every trait is initialized identically, from the parameters given EffectSizeDistributionInfo DES_info; - DES_info.default_dominance_coeff_ = static_cast(p_dominance_coeff); // note this can be NAN now, representing independent dominance + DES_info.default_dominance_coeff_ = static_cast(p_default_dominance); // note this can be NAN now, representing independent dominance DES_info.default_hemizygous_dominance_coeff_ = 1.0; DES_info.DES_type_ = p_DES_type; DES_info.DES_parameters_ = p_DES_parameters; diff --git a/core/mutation_type.h b/core/mutation_type.h index 428e74dc..b024fcab 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -221,9 +221,9 @@ class MutationType : public EidosDictionaryUnretained MutationType& operator=(const MutationType&) = delete; // no copying MutationType(void) = delete; // no null construction #ifdef SLIMGUI - MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings, int p_mutation_type_index); + MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_default_dominance, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings, int p_mutation_type_index); #else - MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings); + MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_default_dominance, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings); #endif ~MutationType(void); diff --git a/core/slim_test_core.cpp b/core/slim_test_core.cpp index 077c330e..8d28c9b9 100644 --- a/core/slim_test_core.cpp +++ b/core/slim_test_core.cpp @@ -44,7 +44,7 @@ void _RunInitTests(void) SLiMAssertScriptRaise("initialize() { initializeGeneConversion(0.5, 1000, 0.0, 1.001); stop(); }", "bias must be between -1.0 and 1.0", __LINE__); SLiMAssertScriptRaise("initialize() { initializeGeneConversion(0.5, 1000, 0.0, 0.1); stop(); }", "must be 0.0 in non-nucleotide-based models", __LINE__); - // Test (object$)initializeMutationType(is$ id, numeric$ dominanceCoeff, string$ distributionType, ...) + // Test (object$)initializeMutationType(is$ id, numeric$ defaultDominance, string$ distributionType, ...) SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0); stop(); }", __LINE__); SLiMAssertScriptStop("initialize() { initializeMutationType(1, 0.5, 'f', 0.0); stop(); }", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType(-1, 0.5, 'f', 0.0); stop(); }", "identifier value is out of range", __LINE__); diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index bebea1a0..df648320 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1313,7 +1313,7 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } // Test independent dominance and new Mutation and MutationType APIs SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5); if (m1.defaultDominanceForTrait(0) == 0.5) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN); if (isNAN(m1.defaultDominanceForTrait(0))) stop(); }"); - SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', INF); }", "requires dominanceCoeff to be finite", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', INF); }", "requires defaultDominance to be finite", __LINE__); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(0, NAN); if (isNAN(m1.defaultDominanceForTrait(0))) stop(); }"); SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(0, INF); }", "default dominance is infinite", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultHemizygousDominanceForTrait(0, NAN); }", "hemizygous dominance is non-finite", __LINE__); diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 2239f6b1..7ea7ea5a 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -600,8 +600,8 @@ EidosValue_SP Species::ExecuteContextFunction_initializeGenomicElementType(const return symbol_entry.second; } -// ********************* (object$)initializeMutationType(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...) -// ********************* (object$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...) +// ********************* (object$)initializeMutationType(is$ id, numeric$ defaultDominance, [Ns$ distributionType = NULL], ...) +// ********************* (object$)initializeMutationTypeNuc(is$ id, numeric$ defaultDominance, [Ns$ distributionType = NULL], ...) // EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -618,7 +618,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeMutationType): initializeMutationTypeNuc() may be only be called in nucleotide-based models." << EidosTerminate(); EidosValue *id_value = p_arguments[0].get(); - EidosValue *dominanceCoeff_value = p_arguments[1].get(); + EidosValue *defaultDominance_value = p_arguments[1].get(); EidosValue *distributionType_value = p_arguments[2].get(); std::ostream &output_stream = p_interpreter.ExecutionOutputStream(); @@ -629,10 +629,10 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: defaultDistribution = true; slim_objectid_t map_identifier = SLiM_ExtractObjectIDFromEidosValue_is(id_value, 0, 'm'); - double dominance_coeff = dominanceCoeff_value->NumericAtIndex_NOCAST(0, nullptr); + double default_dominance = defaultDominance_value->NumericAtIndex_NOCAST(0, nullptr); - if (!std::isfinite(dominance_coeff) && !std::isnan(dominance_coeff)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeMutationType): " << p_function_name << "() requires dominanceCoeff to be finite, or NAN to represent independent dominance." << EidosTerminate(); + if (!std::isfinite(default_dominance) && !std::isnan(default_dominance)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeMutationType): " << p_function_name << "() requires defaultDominance to be finite, or NAN to represent independent dominance." << EidosTerminate(); std::string DES_type_string = (defaultDistribution ? "f" : distributionType_value->StringAtIndex_NOCAST(0, nullptr)); @@ -660,9 +660,9 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: #ifdef SLIMGUI // each new mutation type gets a unique zero-based index, used by SLiMgui to categorize mutations - MutationType *new_mutation_type = new MutationType(*this, map_identifier, dominance_coeff, nucleotide_based, DES_type, DES_parameters, DES_strings, num_mutation_type_inits_); + MutationType *new_mutation_type = new MutationType(*this, map_identifier, default_dominance, nucleotide_based, DES_type, DES_parameters, DES_strings, num_mutation_type_inits_); #else - MutationType *new_mutation_type = new MutationType(*this, map_identifier, dominance_coeff, nucleotide_based, DES_type, DES_parameters, DES_strings); + MutationType *new_mutation_type = new MutationType(*this, map_identifier, default_dominance, nucleotide_based, DES_type, DES_parameters, DES_strings); #endif mutation_types_.emplace(map_identifier, new_mutation_type); @@ -684,7 +684,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: } else { - output_stream << p_function_name << "(" << map_identifier << ", " << dominance_coeff; + output_stream << p_function_name << "(" << map_identifier << ", " << default_dominance; if (defaultDistribution) { From a89a5973ac2cc9a30475b671141ce374c1644cc2 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 22 Jan 2026 22:33:56 -0600 Subject: [PATCH 090/107] some doc tweaks for multitrait terminology --- QtSLiM/help/SLiMHelpClasses.html | 53 ++++---- QtSLiM/help/SLiMHelpFunctions.html | 8 +- SLiMgui/SLiMHelpClasses.rtf | 192 +++++++++++++++++++---------- SLiMgui/SLiMHelpFunctions.rtf | 16 +-- 4 files changed, 163 insertions(+), 106 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 5124088f..8342b7b7 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -268,7 +268,7 @@

Note that in nonWF models that use tree-sequence recording, mutations cannot be added to an individual after the tick in which the individual is created (i.e., when the age of the individual is greater than 0), to prevent the possibility of inconsistencies in the recorded tree sequence.

+ (object<Mutation>)addNewDrawnMutation(io<MutationType> mutationType, integer position, [Nio<Subpopulation> originSubpop = NULL], [Nis nucleotide = NULL])

Add new mutations to the target haplosomes with the specified mutationType (specified by the MutationType object or by integer identifier), position, originTick (which may be NULL, the default, to specify the current tick; otherwise, beginning in SLiM 3.5, it must be equal to the current tick anyway, as other uses of this property have been deprecated), and originSubpop (specified by the Subpopulation object or by integer identifier, or by NULL, the default, to specify the subpopulation to which the first target haplosome belongs).  If originSubpop is supplied as an integer, it is intentionally not checked for validity; you may use arbitrary values of originSubpop to “tag” the mutations that you create (see section 26.10.1).  The effect sizes of the mutations are drawn from the distributions of effect size of their mutation types; addNewMutation() may be used instead if you wish to specify effect sizes.  All of the target haplosomes must be associated with the same Chromosome object, since each new mutation is added to all of the target haplosomes.

-

In non-nucleotide-based models, mutationType will always be a non-nucleotide-based mutation type, and so nucleotide must be NULL (the default).  In a nucleotide-based model, mutationType might still be non-nucleotide-based (in which case nucleotide must still be NULL), or mutationType might be nucleotide-based, in which case a non-NULL value must be supplied for nucleotide, specifying the nucleotide(s) to be associated with the new mutation(s).  Nucleotides may be specified with string values ("A", "C", "G", or "T"), or with integer values (A=0, C=1, G=2, T=3).  If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types.  No check is performed that a new mutation’s nucleotide differs from the ancestral sequence, or that its selection coefficient is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.

+

In non-nucleotide-based models, mutationType will always be a non-nucleotide-based mutation type, and so nucleotide must be NULL (the default).  In a nucleotide-based model, mutationType might still be non-nucleotide-based (in which case nucleotide must still be NULL), or mutationType might be nucleotide-based, in which case a non-NULL value must be supplied for nucleotide, specifying the nucleotide(s) to be associated with the new mutation(s).  Nucleotides may be specified with string values ("A", "C", "G", or "T"), or with integer values (A=0, C=1, G=2, T=3).  If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types.  No check is performed that a new mutation’s nucleotide differs from the ancestral sequence, or that its effect size is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.

Beginning in SLiM 2.5 this method is vectorized, so all of these parameters may be singletons (in which case that single value is used for all mutations created by the call) or non-singleton vectors (in which case one element is used for each corresponding mutation created).  Non-singleton parameters must match in length, since their elements need to be matched up one-to-one.

The new mutations created by this method are returned, even if their actual addition is prevented by the mutation stacking policy (see the mutationStackPolicy property of MutationType).  However, the order of the mutations in the returned vector is not guaranteed to be the same as the order in which the values are specified in parameter vectors, unless the position parameter is specified in ascending order.  In other words, pre-sorting the parameters to this method into ascending order by position, using order() and subsetting, will guarantee that the order of the returned vector of mutations corresponds to the order of elements in the parameters to this method; otherwise, no such guarantee exists.

Beginning in SLiM 2.1, this is a class method, not an instance method.  This means that it does not get multiplexed out to all of the elements of the receiver (which would add a different new mutation to each element); instead, it is performed as a single operation, adding the same new mutation objects to all of the elements of the receiver.  Before SLiM 2.1, to add the same mutations to multiple haplosomes, it was necessary to call addNewDrawnMutation() on one of the haplosomes, and then add the returned Mutation object to all of the other haplosomes using addMutations().  That is not necessary in SLiM 2.1 and later, because of this change (although doing it the old way does no harm and produces identical behavior).  Pre-2.1 code that actually relied upon the old multiplexing behavior will no longer work correctly (but this is expected to be an extremely rare pattern of usage).

@@ -276,8 +276,8 @@

Calling this will normally affect the fitness values calculated at the end of the current tick (but not sooner); if you want current fitness values to be affected, you can call the Species method recalculateFitness() – but see the documentation of that method for caveats.

Note that in nonWF models that use tree-sequence recording, mutations cannot be added to an individual after the tick in which the individual is created (i.e., when the age of the individual is greater than 0), to prevent the possibility of inconsistencies in the recorded tree sequence.

+ (object<Mutation>)addNewMutation(io<MutationType> mutationType, numeric effectSize, integer position, [Nio<Subpopulation> originSubpop = NULL], [Nis nucleotide = NULL])

-

Add new mutations to the target haplosomes with the specified mutationType (specified by the MutationType object or by integer identifier), effectSize, position, originTick (which may be NULL, the default, to specify the current tick; otherwise, beginning in SLiM 3.5, it must be equal to the current tick anyway, as other uses of this property have been deprecated), and originSubpop (specified by the Subpopulation object or by integer identifier, or by NULL, the default, to specify the subpopulation to which the first target haplosome belongs).  If originSubpop is supplied as an integer, it is intentionally not checked for validity; you may use arbitrary values of originSubpop to “tag” the mutations that you create.  The addNewDrawnMutation() method may be used instead if you wish selection coefficients to be drawn from the mutation types of the mutations.  All of the target haplosomes must be associated with the same Chromosome object, since each new mutation is added to all of the target haplosomes.

-

In non-nucleotide-based models, mutationType will always be a non-nucleotide-based mutation type, and so nucleotide must be NULL (the default).  In a nucleotide-based model, mutationType might still be non-nucleotide-based (in which case nucleotide must still be NULL), or mutationType might be nucleotide-based, in which case a non-NULL value must be supplied for nucleotide, specifying the nucleotide(s) to be associated with the new mutation(s).  Nucleotides may be specified with string values ("A", "C", "G", or "T"), or with integer values (A=0, C=1, G=2, T=3).  If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types.  No check is performed that a new mutation’s nucleotide differs from the ancestral sequence, or that its selection coefficient is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.

+

Add new mutations to the target haplosomes with the specified mutationType (specified by the MutationType object or by integer identifier), effectSize, position, originTick (which may be NULL, the default, to specify the current tick; otherwise, beginning in SLiM 3.5, it must be equal to the current tick anyway, as other uses of this property have been deprecated), and originSubpop (specified by the Subpopulation object or by integer identifier, or by NULL, the default, to specify the subpopulation to which the first target haplosome belongs).  If originSubpop is supplied as an integer, it is intentionally not checked for validity; you may use arbitrary values of originSubpop to “tag” the mutations that you create.  The addNewDrawnMutation() method may be used instead if you wish effect sizes to be drawn from the mutation types of the mutations.  All of the target haplosomes must be associated with the same Chromosome object, since each new mutation is added to all of the target haplosomes.

+

In non-nucleotide-based models, mutationType will always be a non-nucleotide-based mutation type, and so nucleotide must be NULL (the default).  In a nucleotide-based model, mutationType might still be non-nucleotide-based (in which case nucleotide must still be NULL), or mutationType might be nucleotide-based, in which case a non-NULL value must be supplied for nucleotide, specifying the nucleotide(s) to be associated with the new mutation(s).  Nucleotides may be specified with string values ("A", "C", "G", or "T"), or with integer values (A=0, C=1, G=2, T=3).  If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types.  No check is performed that a new mutation’s nucleotide differs from the ancestral sequence, or that its effect size is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.

The new mutations created by this method are returned, even if their actual addition is prevented by the mutation stacking policy (see the mutationStackPolicy property of MutationType).  However, the order of the mutations in the returned vector is not guaranteed to be the same as the order in which the values are specified in parameter vectors, unless the position parameter is specified in ascending order.  In other words, pre-sorting the parameters to this method into ascending order by position, using order() and subsetting, will guarantee that the order of the returned vector of mutations corresponds to the order of elements in the parameters to this method; otherwise, no such guarantee exists.

Beginning in SLiM 2.1, this is a class method, not an instance method.  This means that it does not get multiplexed out to all of the elements of the receiver (which would add a different new mutation to each element); instead, it is performed as a single operation, adding the same new mutation object to all of the elements of the receiver.  Before SLiM 2.1, to add the same mutation to multiple haplosomes, it was necessary to call addNewMutation() on one of the haplosomes, and then add the returned Mutation object to all of the other haplosomes using addMutations().  That is not necessary in SLiM 2.1 and later, because of this change (although doing it the old way does no harm and produces identical behavior).  Pre-2.1 code that actually relied upon the old multiplexing behavior will no longer work correctly (but this is expected to be an extremely rare pattern of usage).

Before SLiM 4, this method also took a originGeneration parameter.  This was deprecated (the origin generation was then required to be equal to the current generation, for internal consistency), and was removed in SLiM 4.

@@ -323,14 +323,14 @@

Returns the positions of mutations that are of the type specified by mutType, out of all of the mutations in the haplosome.  If you need a vector of the matching Mutation objects, rather than just positions, use -mutationsOfType().  This method is provided for speed; it is much faster than the corresponding Eidos code.

+ (object<Mutation>)readHaplosomesFromMS(string$ filePath, io<MutationType>$ mutationType)

Read new mutations from the MS format file at filePath and add them to the target haplosomes.  The number of target haplosomes must match the number of haplosomes represented in the MS file, and all target haplosomes must be associated with the same chromosome, and must not be null haplosomes.  The target haplosomes correspond, in order, to the call lines in the MS file.  To read into all of the non-null haplosomes in a given subpopulation pN in a single-chromosome model, simply call pN.haplosomesNonNull.readHaplosomesFromMS(), assuming the subpopulation’s size matches that of the MS file.  A vector containing all of the mutations created by readHaplosomesFromMS() is returned.

-

Each mutation is created at the position specified in the file, using the mutation type given by mutationType.  Positions are expected to be in [0,1], and are scaled to the length of the chromosome by multiplying by the last valid base position of the chromosome (i.e., one less than the chromosome length).  Selection coefficients are drawn from the mutation type.  The population of origin for each mutation is set to -1, and the tick of origin is set to the current tick.  In a nucleotide-based model, if mutationType is nucleotide-based, a random nucleotide different from the ancestral nucleotide at the position will be chosen with equal probability.

+

Each mutation is created at the position specified in the file, using the mutation type given by mutationType.  Positions are expected to be in [0,1], and are scaled to the length of the chromosome by multiplying by the last valid base position of the chromosome (i.e., one less than the chromosome length).  Effect sizes are drawn from the mutation type.  The population of origin for each mutation is set to -1, and the tick of origin is set to the current tick.  In a nucleotide-based model, if mutationType is nucleotide-based, a random nucleotide different from the ancestral nucleotide at the position will be chosen with equal probability.

+ (object<Mutation>)readHaplosomesFromVCF(string$ filePath, [Nio<MutationType>$ mutationType = NULL])

Read new mutations from the VCF format file at filePath and add them to the target haplosomes.  The number of target haplosomes must match the number of haplosomes represented in the VCF file (i.e., two times the number of diploid samples plus one times the number of haploid samples).  To read into all of the haplosomes in a given subpopulation pN in a single-chromosome model, simply call pN.haplosomes.readHaplosomesFromVCF(), assuming the subpopulation’s size matches that of the VCF file taking ploidy into account.  A vector containing all of the mutations created by readHaplosomesFromVCF() is returned.

This method and the readIndividualsFromVCF() method of Individual provide two alternative ways of reading VCF data, focused in the perspective of either haplosomes (this method) or individuals (the Individual method).  See the documentation of readIndividualsFromVCF() for discussion of the pros and cons of each approach; that discussion will not be duplicated here.

SLiM’s VCF parsing is quite primitive.  The header is parsed only inasmuch as SLiM looks to see whether SLiM-specific VCF fields are defined or not; the rest of the header information is ignored.  Call lines are assumed to follow the format:

#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT i0...iN

The CHROM field is largely ignored, but readHaplosomesFromVCF() does check that its value is identical across all call lines, to prevent the genetic data for more than one chromosome from being glommed together nonsensically; the input VCF file must contain data for just a single chromosome.  In single-chromosome models the CHROM field is not otherwise checked or validated.  In multi-chromosome models, readHaplosomesFromVCF() imposes some additional restrictions.  First, all haplosomes in the target vector must be associated with the same single focal chromosome.  Second, the CHROM field for every call line in the VCF file must match the symbol property of that focal chromosome; the VCF file must indicate that it specifically matches the focal chromosome associated with the target haplosomes.  These restrictions boil down to the fact that readHaplosomesFromVCF() only reads data for a single chromosome.  If you wish to read multi-chromosome VCF data into a multi-chromosome SLiM model, the readIndividualsFromVCF() method provided by the Individual class supports that functionality (because it can work at the level of individuals, rather than haplosomes, making it possible to match calls to the corresponding haplosomes in a reasonable way).  Alternatively, you can call readHaplosomesFromVCF() multiple times to read data for different chromosomes one by one.

-

The ID, QUAL, FILTER, and FORMAT fields are ignored, and information in the genotype fields beyond the GT genotype subfield are also ignored.  SLiM’s own VCF annotations are honored; in particular, mutations will be created using the given values of MID, S, PO, TO, and MT if those subfields are present, and DOM, if it is present, must match the dominance coefficient of the mutation type.  The parameter mutationType (a MutationType object or id) will be used for any mutations that have no supplied mutation type id in the MT subfield; if mutationType would be used but is NULL an error will result.  Mutation IDs supplied in MID will be used if no mutation IDs have been used in the simulation so far; if any have been used, it is difficult for SLiM to guarantee that there are no conflicts, so a warning will be emitted and the MID values will be ignored.  If selection coefficients are not supplied with the S subfield, they will be drawn from the mutation type used for the mutation.  If a population of origin is not supplied with the PO subfield, -1 will be used.  If a tick of origin is not supplied with the TO subfield (or a generation of origin GO field, which was the SLiM convention before SLiM 4), the current tick will be used.

+

The ID, QUAL, FILTER, and FORMAT fields are ignored, and information in the genotype fields beyond the GT genotype subfield are also ignored.  SLiM’s own VCF annotations are honored; in particular, mutations will be created using the given values of MID, S, PO, TO, and MT if those subfields are present, and DOM, if it is present, must match the dominance coefficient of the mutation type.  The parameter mutationType (a MutationType object or id) will be used for any mutations that have no supplied mutation type id in the MT subfield; if mutationType would be used but is NULL an error will result.  Mutation IDs supplied in MID will be used if no mutation IDs have been used in the simulation so far; if any have been used, it is difficult for SLiM to guarantee that there are no conflicts, so a warning will be emitted and the MID values will be ignored.  If effect sizes are not supplied with the S subfield, they will be drawn from the mutation type used for the mutation.  If a population of origin is not supplied with the PO subfield, -1 will be used.  If a tick of origin is not supplied with the TO subfield (or a generation of origin GO field, which was the SLiM convention before SLiM 4), the current tick will be used.

REF and ALT must always be comprised of simple nucleotides (A/C/G/T) rather than values representing indels or other complex states.  Beyond this, the handling of the REF and ALT fields depends upon several factors.  In non-nucleotide-based models, we have the first case: (1) These fields are ignored, although they are still checked for conformance.  In nucleotide-based models, when a header definition for SLiM’s NONNUC tag is present (as when nucleotide-based output is generated by SLiM) there are two further possibilities, given as (2) and (3) here: (2) If a NONNUC field is present in the INFO field the call line is taken to represent a non-nucleotide-based mutation, and REF and ALT are again ignored; in this case the mutation type used must be non-nucleotide-based.  (3) If a NONNUC field is not present the call line is taken to represent a nucleotide-based mutation; in this case, the mutation type used must be nucleotide-based, and the specified reference nucleotide must match the existing ancestral nucleotide at the given position.  Finally, in nucleotide-based models, when a header definition for SLiM’s NONNUC tag is not present (as when loading a non-SLiM-generated VCF file), there is a remaining possibility: (4) The mutation type used will govern the way nucleotides are handled.  In this case, if the mutation type used for a mutation is nucleotide-based, the nucleotide provided in the VCF file for that allele will be used, whereas if the mutation type is non-nucleotide-based, the nucleotide provided will be ignored.

If multiple alleles using the same nucleotide at the same position are specified in the VCF file, a separate mutation will be created for each, mirroring SLiM’s behavior with independent mutational lineages when writing VCF.  The MULTIALLELIC flag is ignored by readHaplosomesFromVCF(); call lines for mutations at the same base position in the same haplosome will result in stacked mutations whether or not MULTIALLELIC is present.

The target haplosomes correspond, in order, to the haploid or diploid calls provided for i0iN (the sample IDs) in the VCF file.  Null haplosomes in the target vector will be skipped, and will not be used to correspond to any of the calls for i0iN; however, care should be taken in this case that the haplosomes in the VCF file correspond to the target haplosomes in the manner desired.

@@ -703,14 +703,14 @@

chromosome => (object<Chromosome>$)

The Chromosome object with which the mutation is associated.

dominance => (float)

-

The dominance coefficient(s) of the mutation, taken from the default dominance coefficient(s) of its MutationType.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightDominance to access the dominance for that trait.  The dominance coefficient(s) of a mutation can be changed with the setDominanceForTrait() method.

+

The dominance coefficient(s) of the mutation, taken from the default dominance coefficient(s) of its MutationType.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that a dynamic property named <trait-name>Dominance is defined on Mutation for each trait in the species, providing direct access to the dominance of a mutation for a specific trait.  The dominance coefficient(s) of a mutation can be changed with the setDominanceForTrait() method, or through the <trait-name>Dominance property.

If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this property does not provide that value of NAN; instead, it provides the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance value (see isIndependentDominanceForTrait() for the way to determine whether independent dominance is configured for a trait).  This realized dominance value depends upon the mutation’s corresponding effect size, and may change if the effect size changes.  The class Trait documentation provides further discussion of independent dominance.

Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s dominance coefficient to some number x, mut.dominance==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

effectSize => (float)

-

The effect size(s) of the mutation, drawn from the distribution of effect size of its MutationType.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectSizeForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightEffectSize to access the effect size for that trait.  The effect sizes of a mutation can be changed with the setEffectSizeForTrait() method.

+

The effect size(s) of the mutation, initially drawn from the distribution of effect size of its MutationType.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectSizeForTrait() method.  Also note that a dynamic property named <trait-name>EffectSize is defined on Mutation for each trait in the species, providing direct access to the effect size of a mutation for a specific trait.  The effect sizes of a mutation can be changed with the setEffectSizeForTrait() method, or through the <trait-name>EffectSize property.

Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s effect size to some number x, mut.effectSize==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

hemizygousDominance => (float)

-

The hemizygous dominance coefficient(s) of the mutation, taken from the default hemizygous dominance coefficient(s) of its MutationType.  In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the hemizygousDominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Mutation objects will have a dynamic property named heightHemizygousDominance to access the hemizygous dominance for that trait.  The hemizygous dominance coefficient(s) of a mutation can be changed with the setHemizygousDominanceForTrait() method.

+

The hemizygous dominance coefficient(s) of the mutation, taken from the default hemizygous dominance coefficient(s) of its MutationType.  In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the hemizygousDominanceForTrait() method.  Also note that a dynamic property named <trait-name>HemizygousDominance is defined for each trait in the model, providing direct access to the hemizygous dominance of a mutation for a specific trait.  The coefficient(s) for a mutation can be changed with the setHemizygousDominanceForTrait() method, or through the <trait-name>HemizygousDominance property.

Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos.  This means that if you set a mutation mut’s hemizygous dominance coefficient to some number x, mut.hemizygousDominance==x may be F due to floating-point rounding error.  Comparisons of floating-point numbers for exact equality is often a bad idea, but this is one case where it may fail unexpectedly.

id => (integer$)

The identifier for this mutation.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run.  These identifiers are not re-used during a run, except that if a population file is loaded from disk, the loaded mutations will receive their original identifier values as saved in the population file.

@@ -737,24 +737,24 @@

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.

5.10.2  Mutation methods

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

-

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.  See also the dominance and <trait-value>Dominance properties.

If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this method does not return that value of NAN; instead, it returns the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance value (see isIndependentDominanceForTrait() for the way to determine whether independent dominance is configured for a trait).  This realized dominance value depends upon the mutation’s corresponding effect size, and may change if that effect size changes.  The class Trait documentation provides further discussion of independent dominance.

– (float)effectSizeForTrait([Niso<Trait> trait = NULL])

-

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.  See also the effectSize and <trait-value>EffectSize properties.

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

-

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.  See also the hemizygousDominance and <trait-value>HemizygousDominance properties.

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

Returns whether the mutation is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A mutation is configured for independent dominance for a trait if it inherited a default dominance of NAN for the trait from its mutation type, or if its dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation for discussion of this feature.  Independent dominance flags for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+ (void)setDominanceForTrait([Niso<Trait> trait = NULL], [Nif dominance = NULL])

-

Sets the target mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

Sets the target mutation’s dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  See also the <trait-name>Dominance property, which can be used to set the dominance of a mutation for a trait.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default dominance values.)  In the second pattern, dominance is a singleton value; this sets the given dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the dominance for each of the specified traits in one mutation before moving to the next mutation.

A dominance value of NAN for a given trait configures the mutation to use independent dominance for that trait; see the class Trait documentation for discussion of this feature, which can also be set at the MutationType level with initializeMutationType() or setDefaultDominanceForTrait().  A given mutation may be configured to exhibit independent dominance for some traits and not for others; a mixed configuration is allowed.

+ (void)setEffectSizeForTrait([Niso<Trait> trait = NULL], [Nif effectSize = NULL])

-

Sets the mutation’s effect size(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

Sets the mutation’s effect size(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  See also the <trait-name>EffectSize property, which can be used to set the effect size of a mutation for a trait.

The parameter effectSize must follow one of four patterns.  In the first pattern, effectSize is NULL; this draws the effect size for each of the specified traits from the corresponding distribution of effect size for the mutation type of the mutation in each target mutation.  (Note that mutation effect sizes are automatically drawn from these distributions when a mutation is created; this re-draws new effect sizes.)  In the second pattern, effectSize is a singleton value; this sets the given effect size for each of the specified traits in each target mutation.  In the third pattern, effectSize is of length equal to the number of specified traits; this sets the effect size for each of the specified traits to the corresponding effect size in each target mutation.  In the fourth pattern, effectSize is of length equal to the number of specified traits times the number of target mutations; this uses effectSize to provide a different effect size for each trait in each mutation, using consecutive values from effectSize to set the effect size for each of the specified traits in one mutation before moving to the next mutation.

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  This policy applies to mutational effects, so the final effect of each mutation (perhaps influenced by a dominance coefficient) will be clamped prior to use in phenotype calculations.

+ (void)setHemizygousDominanceForTrait([Niso<Trait> trait = NULL], [Nif dominance = NULL])

-

Sets the mutation’s hemizygous dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.

+

Sets the mutation’s hemizygous dominance coefficient(s) for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  See also the <trait-name>HemizygousDominance property, which can be used to set the hemizygous dominance of a mutation for a trait.

The parameter dominance must follow one of four patterns.  In the first pattern, dominance is NULL; this sets the dominance for each of the specified traits to the default hemizygous dominance coefficient from the mutation type of the mutation in each target mutation.  (Note that mutation hemizygous dominance coefficients are automatically set to these defaults when a mutation is created; this re-sets default hemizygous dominance values.)  In the second pattern, dominance is a singleton value; this sets the given hemizygous dominance for each of the specified traits in each target mutation.  In the third pattern, dominance is of length equal to the number of specified traits; this sets the hemizygous dominance for each of the specified traits to the corresponding dominance value in each target mutation.  In the fourth pattern, dominance is of length equal to the number of specified traits times the number of target mutations; this uses dominance to provide a different hemizygous dominance coefficient for each trait in each mutation, using consecutive values from dominance to set the hemizygous dominance for each of the specified traits in one mutation before moving to the next mutation.

– (void)setMutationType(io<MutationType>$ mutType)

Set the mutation type of the mutation to mutType (which may be specified as either an integer identifier or a MutationType object).  The effect sizes and dominance coefficients of existing mutations are not changed, since those are properties of the mutation objects themselves; they can be changed explicitly using the setEffectSizeForTrait() and setDominanceForTrait() methods of Mutation if so desired.

@@ -762,9 +762,9 @@

5.11  Class MutationType

5.11.1  MutationType properties

color <–> (string$)

-

The color used to display mutations of this type in SLiMgui.  Outside of SLiMgui, this property still exists, but is not used by SLiM.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB".  If color is the empty string, "", SLiMgui’s default (selection-coefficient–based) color scheme is used; this is the default for new MutationType objects.

+

The color used to display mutations of this type in SLiMgui.  Outside of SLiMgui, this property still exists, but is not used by SLiM.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB".  If color is the empty string, "", SLiMgui’s default (effect-size–based) color scheme is used; this is the default for new MutationType objects.

colorSubstitution <–> (string$)

-

The color used to display substitutions of this type in SLiMgui (see the discussion for the colorSubstitution property of the Chromosome class for details).  Outside of SLiMgui, this property still exists, but is not used by SLiM.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB".  If colorSubstitution is the empty string, "", SLiMgui’s default (selection-coefficient–based) color scheme is used; this is the default for new MutationType objects.

+

The color used to display substitutions of this type in SLiMgui (see the discussion for the colorSubstitution property of the Chromosome class for details).  Outside of SLiMgui, this property still exists, but is not used by SLiM.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB".  If colorSubstitution is the empty string, "", SLiMgui’s default (effect-size–based) color scheme is used; this is the default for new MutationType objects.

convertToSubstitution <–> (logical$)

This property governs whether mutations of this mutation type will be converted to Substitution objects when they reach fixation.

In WF models this property is T by default, since conversion to Substitution objects provides large speed benefits; it should be set to F only if necessary, and only on the mutation types for which it is necessary.  This might be needed, for example, if you are using a mutationEffect() callback to implement an epistatic relationship between mutations; a mutation epistatically influencing the fitness of other mutations through a mutationEffect() callback would need to continue having that influence even after reaching fixation, but if the simulation were to replace the fixed mutation with a Substitution object the mutation would no longer be considered in fitness calculations (unless the callback explicitly consulted the list of Substitution objects kept by the simulation).  Other script-defined behaviors in mutationEffect(), interaction(), mateChoice(), modifyChild(), and recombination() callbacks might also necessitate the disabling of substitution for a given mutation type; this is an important consideration to keep in mind.

@@ -790,11 +790,10 @@

– (float)defaultDominanceForTrait([Niso<Trait> trait = NULL])

Returns the default dominance coefficient used for the specified trait or traits, for mutations of this type when heterozygous.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their dominance property, but that can be changed later with the Mutation method setDominanceForTrait().

If a default dominance coefficient of NAN has been set for a given trait, to configure a default of “independent dominance” for that trait, NAN will be returned by this method for that trait; a realized dominance value cannot be returned since the corresponding effect size is not necessarily known.  See the Mutation method dominanceForTrait() regarding obtaining the realized dominance coefficient used in a particular mutation, given that mutation’s effect size for the trait.

-

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

+

Note that dominance coefficients are not bounded.  For multiplicative traits, dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the effect size very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

– (float)defaultHemizygousDominanceForTrait([Niso<Trait> trait = NULL])

Returns the default hemizygous dominance coefficient used for the specified trait or traits, for mutations of this type when hemizygous (i.e., when present in only one copy because they are facing a null haplosome, such as for an X chromosome in a male).  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The default hemizygous dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their hemizygousDominance property, but that can be changed later with the Mutation method setHemizygousDominanceForTrait().

-

Note that dominance coefficients are not bounded.  A dominance coefficient greater than 1.0 may be used to achieve an overdominance effect.  By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.

Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision float, not the double-precision float type normally used by Eidos.  This means that comparisons of dominance coefficients for exact equality might fail unexpectedly, due to roundoff.

– (float)drawEffectSizeForTrait([Niso<Trait> trait = NULL], [integer$ n = 1])

Draws and returns a vector of n mutation effect sizes using the distribution of effect size for the specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  See the MutationType class documentation for discussion of the supported distributions and their uses.  If the distribution of effect size is of type "s", this method will result in synchronous execution of the script associated with the distribution of effect size.

@@ -1040,7 +1039,7 @@

tag <–> (integer$)

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.  See also the getValue() and setValue() methods (provided by the Dictionary class; see the Eidos manual), for another way of attaching state to the simulation.

traits => (object<Trait>)

-

The Trait objects defined in the species.  These are guaranteed to be in sorted order by their index (i.e., by the order in which they were defined).

+

The Trait objects defined in the species.  These are guaranteed to be in sorted order by their index (i.e., by the order in which they were defined).  The traitsWithIndices() and traitsWithNames() methods and the <trait-name> property are also useful for obtaining particular Trait objects from the species.

5.16.2  Species methods

– (object<Dictionary>$)addPatternForClone(iso<Chromosome>$ chromosome, No<Dictionary>$ pattern, object<Individual>$ parent, [Ns$ sex = NULL])

Adds an inheritance dictionary for the specified chromosome to the pattern dictionary pattern, representing producing a clone of parent, with sex optionally specified by sex.  The parameter chromosome can provide a chromosome id (an integer), a chromosome symbol (a string), or a Chromosome object.  The resulting pattern dictionary is intended for use with the Subpopulation method addMultiRecombinant(); see that method for background on the use of pattern dictionaries.

@@ -1163,9 +1162,9 @@

– (object<Substitution>)substitutionsOfType(io<MutationType>$ mutType)

Returns an object vector of all the substitutions that are of the type specified by mutType, out of all of the substitutions that are currently present in the species.  This method is provided for speed; it is much faster than the corresponding Eidos code.  See also mutationsOfType().

– (object<Trait>)traitsWithIndices(integer indices)

-

Returns a vector of Trait objects corresponding to the trait indices supplied in indices, in the same order.  If any index in indices does not correspond to a trait in the target species, an error will be raised.  See also traitsWithNames().

+

Returns a vector of Trait objects corresponding to the trait indices supplied in indices, in the same order.  If any index in indices does not correspond to a trait in the target species, an error will be raised.  See also traitsWithNames(), and the traits and <trait-name> properties.

– (object<Trait>)traitsWithNames(string names)

-

Returns a vector of Trait objects corresponding to the trait names supplied in names, in the same order.  If any name in names does not correspond to a trait in the target species, an error will be raised.  See also traitsWithIndices().

+

Returns a vector of Trait objects corresponding to the trait names supplied in names, in the same order.  If any name in names does not correspond to a trait in the target species, an error will be raised.  See also traitsWithIndices(), and the traits and <trait-name> properties.

– (logical$)treeSeqCoalesced(void)

Returns the coalescence state for the recorded tree sequence at the last simplification.  The returned value is a logical singleton flag, T to indicate that full coalescence was observed at the last tree-sequence simplification (meaning that there is a single ancestral individual that roots all ancestry trees at all sites along the chromosome – although not necessarily the same ancestor at all sites), or F if full coalescence was not observed.  For simple models, reaching coalescence may indicate that the model has reached an equilibrium state, but this may not be true in models that modify the dynamics of the model during execution by changing migration rates, introducing new mutations programmatically, dictating non-random mating, etc., so be careful not to attach more meaning to coalescence than it is due; some models may require burn-in beyond coalescence to reach equilibrium, or may not have an equilibrium state at all.  Also note that some actions by a model, such as adding a new subpopulation, may cause the coalescence state to revert from T back to F (at the next simplification), so a return value of T may not necessarily mean that the model is coalesced at the present moment – only that it was coalesced at the last simplification.

This method may only be called if tree sequence recording has been turned on with initializeTreeSeq(); in addition, checkCoalescence=T must have been supplied to initializeTreeSeq(), so that the necessary work is done during each tree-sequence simplification.  Since this method does not perform coalescence checking itself, but instead simply returns the coalescence state observed at the last simplification, it may be desirable to call treeSeqSimplify() immediately before treeSeqCoalesced() to obtain up-to-date information.  However, the speed penalty of doing this in every tick would be large, and most models do not need this level of precision; usually it is sufficient to know that the model has coalesced, without knowing whether that happened in the current tick or in a recent preceding tick.

@@ -1405,11 +1404,11 @@

chromosome => (object<Chromosome>$)

The Chromosome object with which the substitution is associated.

dominance => (float)

-

The dominance coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightDominance to access the dominance for that trait.

+

The dominance coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the dominanceForTrait() method.  Also note that a dynamic property named <trait-name>Dominance is defined on Substitution for each trait in the model, providing direct access to the dominance of a substitution for a specific trait.

effectSize => (float)

-

The effect size(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectSizeForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightEffectSize to access the effect size for that trait.

+

The effect size(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined).  For more control, see the effectSizeForTrait() method.  Also note that a dynamic property named <trait-name>EffectSize is defined on Substitution for each trait in the model, providing direct access to the effect size of a substitution for a specific trait.

hemizygousDominance => (float)

-

The hemizygous dominance coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the hemizygousDominanceForTrait() method.  Also note that dynamic properties are defined for each trait in the model; if there is a trait named height, for example, then Substitution objects will have a dynamic property named heightHemizygousDominance to access the dominance for that trait.

+

The hemizygous dominance coefficient(s) of the substitution, carried over from the original mutation object.  In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined).  For more control, see the hemizygousDominanceForTrait() method.  Also note that a dynamic property named <trait-name>HemizygousDominance is defined on Substitution for each trait in the model, providing direct access to the hemizygous dominance of a substitution for a specific trait.

id => (integer$)

The identifier for this substitution.  Each mutation created during a run receives an immutable identifier that will be unique across the duration of the run, and that identifier is carried over to the Substitution object when the mutation fixes.

isNeutral => (logical$)

@@ -1432,11 +1431,11 @@

A user-defined integer value.  The value of tag is carried over automatically from the original Mutation object.  Apart from that, the value of tag is not used by SLiM; it is free for you to use.

5.18.2  Substitution methods

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

-

Returns the substitution’s dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

Returns the substitution’s dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.  See also the dominance and <trait-value>Dominance properties.

– (float)effectSizeForTrait([Niso<Trait> trait = NULL])

-

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.  See also the effectSize and <trait-value>EffectSize properties.

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

-

Returns the substitution’s hemizygous dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

+

Returns the substitution’s hemizygous dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.  See also the hemizygousDominance and <trait-value>HemizygousDominance properties.

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

Returns whether the substitution is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A substitution is configured for independent dominance for a trait if the original mutation inherited a default dominance of NAN for the trait from its mutation type, or if the original mutation’s dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation for discussion of this feature.  Independent dominance flags for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

5.19  Class Trait

diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 813f4998..019a9717 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -103,12 +103,12 @@

Set a mutation rate map from data read from the file at path.  This function is essentially a wrapper for initializeMutationRate() that uses readCSV() and passes the data through.  The file is expected to contain two columns of data.  The first column must be integer start positions for rate map regions; the first region should start at position 0 if the map’s positions are 0-based, or at position 1 if the map’s positions are 1-based; in the latter case, 1 will be subtracted from every position since SLiM uses 0-based positions.  The second column must be float rates, relative to the scaling factor specified in scale; for example, if a given rate is 1.2 and scale is 1e-8 (the default), the rate used will be 1.2e-8.  No column header line should be present; the file should start immediately with numerical data.  The expected separator between columns is a tab character by default, but may be passed in sep; the expected decimal separator is a period by default, but may be passed in dec.  Once read, the map is converted into a rate map specified with end positions, rather than start positions, and the position given by lastPosition is used as the end of the last rate region; it should be the last position of the chromosome.

See readCSV() for further details on sep and dec, which are passed through to it; and see initializeMutationRate() for details on how the rate map is validated and used, and how the sex parameter is used.

This function is written in Eidos, and its source code can be viewed with functionSource(), so you can copy and modify its code if you need to modify its functionality.

-

(object<MutationType>$)initializeMutationType(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

+

(object<MutationType>$)initializeMutationType(is$ id, numeric$ defaultDominance, [Ns$ distributionType = NULL], ...)

Add a mutation type at initialization time.  The id must not already be used for any mutation type in the simulation.  The id parameter may be either an integer giving the ID of the new mutation type, or a string giving the name of the new mutation type (such as "m5" to specify an ID of 5).  The global symbol for the new mutation type, such as m5, is immediately available; the return value also provides the new object.

-

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  A dominanceCoeff value of NAN configures the mutation type to use “independent dominance” for new mutations of that type, for all traits; see the class Trait documentation for discussion of independent dominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but the setDefaultHemizygousDominanceForTrait() method can configure it subsequently if desired.

-

The distributionType and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits.  The DES for the mutation type for a specific trait can subsequently be separately configured with the setEffectSizeDistributionForTrait() method if desired.  The distributionType parameter may be "f", in which case the ellipsis ... should supply a numeric$ fixed selection coefficient; "e", in which case the ellipsis should supply a numeric$ mean selection coefficient for an exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean selection coefficient and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  If distributionType is NULL (the default), a fixed effect size of 0.0 is used, representing a neutral DES; this is equivalent to type "f" except that the value 0.0 is assumed and must not be supplied.  See the MutationType class documentation for discussion of the various DESs and their uses.

+

The defaultDominance parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  A defaultDominance value of NAN configures the mutation type to use “independent dominance” for new mutations of that type, for all traits; see the class Trait documentation for discussion of independent dominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but the setDefaultHemizygousDominanceForTrait() method can configure it subsequently if desired.

+

The distributionType and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits.  The DES for the mutation type for a specific trait can subsequently be separately configured with the setEffectSizeDistributionForTrait() method if desired.  The distributionType parameter may be "f", in which case the ellipsis ... should supply a numeric$ fixed effect size; "e", in which case the ellipsis should supply a numeric$ mean effect size for an exponential distribution; "g", in which case the ellipsis should supply a numeric$ mean effect size and a numeric$ alpha shape parameter for a gamma distribution; "n", in which case the ellipsis should supply a numeric$ mean effect size and a numeric$ sigma (standard deviation) parameter for a normal distribution; "p", in which case the ellipsis should supply a numeric$ mean effect size and a numeric$ scale parameter for a Laplace distribution; "w", in which case the ellipsis should supply a numeric$ λ scale parameter and a numeric$ k shape parameter for a Weibull distribution; or "s", in which case the ellipsis should supply a string$ Eidos script parameter.  If distributionType is NULL (the default), a fixed effect size of 0.0 is used, representing a neutral DES; this is equivalent to type "f" except that the value 0.0 is assumed and must not be supplied.  See the MutationType class documentation for discussion of the various DESs and their uses.

Note that by default in WF models, all mutations of a given mutation type will be converted into Substitution objects when they reach fixation, for efficiency reasons.  If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the convertToSubstitution property of MutationType to F.  In contrast, by default in nonWF models mutations will not be converted into Substitution objects when they reach fixation; convertToSubstitution is F by default in nonWF models.  To enable conversion in nonWF models for neutral mutation types with no indirect fitness effects, you should therefore set convertToSubstitution to T.

-

(object<MutationType>$)initializeMutationTypeNuc(is$ id, numeric$ dominanceCoeff, [Ns$ distributionType = NULL], ...)

+

(object<MutationType>$)initializeMutationTypeNuc(is$ id, numeric$ defaultDominance, [Ns$ distributionType = NULL], ...)

Add a nucleotide-based mutation type at initialization time.  This function is identical to initializeMutationType() except that the new mutation type will be nucleotide-based – in other words, mutations belonging to the new mutation type will have an associated nucleotide.  This function may be called only in nucleotide-based models (as enabled by the nucleotideBased parameter to initializeSLiMOptions()).

Nucleotide-based mutations always use a mutationStackGroup of -1 and a mutationStackPolicy of "l".  This ensures that a new nucleotide mutation always replaces any previously existing nucleotide mutation at a given position, regardless of the mutation types of the nucleotide mutations.  These values are set automatically by initializeMutationTypeNuc(), and may not be changed.

See the documentation for initializeMutationType() for all other discussion.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index adb5b1cd..4fbeaccd 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -1774,7 +1774,7 @@ In non-nucleotide-based models, \f3\fs18 2 \f4\fs20 , T= \f3\fs18 3 -\f4\fs20 ). If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types. No check is performed that a new mutation\'92s nucleotide differs from the ancestral sequence, or that its selection coefficient is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.\ +\f4\fs20 ). If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types. No check is performed that a new mutation\'92s nucleotide differs from the ancestral sequence, or that its effect size is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 \kerning1\expnd0\expndtw0 Beginning in SLiM 2.5 this method is vectorized, so all of these parameters may be singletons (in which case that single value is used for all mutations created by the call) or non-singleton vectors (in which case one element is used for each corresponding mutation created). Non-singleton parameters must match in length, since their elements need to be matched up one-to-one.\ The new mutations created by this method are returned, even if their actual addition is prevented by the mutation stacking policy (see the @@ -1846,7 +1846,7 @@ Beginning in SLiM 2.1, this is a class method, not an instance method. This mea \f3\fs18 originSubpop \f4\fs20 to \'93tag\'94 the mutations that you create. The \f3\fs18 addNewDrawnMutation() -\f4\fs20 method may be used instead if you wish selection coefficients to be drawn from the mutation types of the mutations. All of the target haplosomes must be associated with the same +\f4\fs20 method may be used instead if you wish effect sizes to be drawn from the mutation types of the mutations. All of the target haplosomes must be associated with the same \f3\fs18 Chromosome \f4\fs20 object, since each new mutation is added to all of the target haplosomes.\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -1885,7 +1885,7 @@ In non-nucleotide-based models, \f3\fs18 2 \f4\fs20 , T= \f3\fs18 3 -\f4\fs20 ). If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types. No check is performed that a new mutation\'92s nucleotide differs from the ancestral sequence, or that its selection coefficient is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.\ +\f4\fs20 ). If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types. No check is performed that a new mutation\'92s nucleotide differs from the ancestral sequence, or that its effect size is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 \kerning1\expnd0\expndtw0 The new mutations created by this method are returned, even if their actual addition is prevented by the mutation stacking policy (see the \f3\fs18 mutationStackPolicy @@ -2467,7 +2467,7 @@ readHaplosomesFromMS\kerning1\expnd0\expndtw0 () \f4\fs20 is returned.\ Each mutation is created at the position specified in the file, using the mutation type given by \f3\fs18 mutationType -\f4\fs20 . Positions are expected to be in [0,1], and are scaled to the length of the chromosome by multiplying by the last valid base position of the chromosome (i.e., one less than the chromosome length). Selection coefficients are drawn from the mutation type. The population of origin for each mutation is set to +\f4\fs20 . Positions are expected to be in [0,1], and are scaled to the length of the chromosome by multiplying by the last valid base position of the chromosome (i.e., one less than the chromosome length). Effect sizes are drawn from the mutation type. The population of origin for each mutation is set to \f3\fs18 -1 \f4\fs20 , and the tick of origin is set to the current tick. In a nucleotide-based model, if \f3\fs18 mutationType @@ -2560,7 +2560,7 @@ The \f3\fs18 MID \f4\fs20 will be used if no mutation IDs have been used in the simulation so far; if any have been used, it is difficult for SLiM to guarantee that there are no conflicts, so a warning will be emitted and the \f3\fs18 MID -\f4\fs20 values will be ignored. If selection coefficients are not supplied with the +\f4\fs20 values will be ignored. If effect sizes are not supplied with the \f3\fs18 S \f4\fs20 subfield, they will be drawn from the mutation type used for the mutation. If a population of origin is not supplied with the \f3\fs18 PO @@ -6247,16 +6247,19 @@ You can get the \f3\fs18 MutationType \f4\fs20 . In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the \f3\fs18 dominanceForTrait() -\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named -\f3\fs18 height -\f4\fs20 , for example, then +\f4\fs20 method. Also note that a dynamic property named +\f2\i\fs18 +\f3\i0 Dominance +\f4\fs20 is defined on \f3\fs18 Mutation -\f4\fs20 objects will have a dynamic property named -\f3\fs18 heightDominance -\f4\fs20 to access the dominance for that trait. The dominance coefficient(s) of a mutation can be changed with the +\f4\fs20 for each trait in the species, providing direct access to the dominance of a mutation for a specific trait. The dominance coefficient(s) of a mutation can be changed with the \f3\fs18 setDominanceForTrait() -\f4\fs20 method.\ -If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to +\f4\fs20 method, or through the +\f2\i\fs18 +\f3\i0 Dominance +\f4\fs20 property.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to \f3\fs18 NAN \f4\fs20 , as discussed in \f3\fs18 initializeMutationType() @@ -6283,20 +6286,23 @@ Note that dominance coefficients in SLiM have a quirk: they are stored internall \f3\fs18 \cf2 effectSize => (float)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The effect size(s) of the mutation, drawn from the distribution of effect size of its +\f4\fs20 \cf2 The effect size(s) of the mutation, initially drawn from the distribution of effect size of its \f3\fs18 MutationType \f4\fs20 . In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the \f3\fs18 effectSizeForTrait() -\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named -\f3\fs18 height -\f4\fs20 , for example, then +\f4\fs20 method. Also note that a dynamic property named +\f2\i\fs18 +\f3\i0 EffectSize +\f4\fs20 is defined on \f3\fs18 Mutation -\f4\fs20 objects will have a dynamic property named -\f3\fs18 heightEffectSize -\f4\fs20 to access the effect size for that trait. The effect sizes of a mutation can be changed with the +\f4\fs20 for each trait in the species, providing direct access to the effect size of a mutation for a specific trait. The effect sizes of a mutation can be changed with the \f3\fs18 setEffectSizeForTrait() -\f4\fs20 method.\ -Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +\f4\fs20 method, or through the +\f2\i\fs18 +\f3\i0 EffectSize +\f4\fs20 property.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s effect size to some number \f3\fs18 x @@ -6314,16 +6320,17 @@ Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM \f3\fs18 MutationType \f4\fs20 . In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the \f3\fs18 hemizygousDominanceForTrait() -\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named -\f3\fs18 height -\f4\fs20 , for example, then -\f3\fs18 Mutation -\f4\fs20 objects will have a dynamic property named -\f3\fs18 heightHemizygousDominance -\f4\fs20 to access the hemizygous dominance for that trait. The hemizygous dominance coefficient(s) of a mutation can be changed with the +\f4\fs20 method. Also note that a dynamic property named +\f2\i\fs18 +\f3\i0 HemizygousDominance +\f4\fs20 is defined for each trait in the model, providing direct access to the hemizygous dominance of a mutation for a specific trait. The coefficient(s) for a mutation can be changed with the \f3\fs18 setHemizygousDominanceForTrait() -\f4\fs20 method.\ -Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +\f4\fs20 method, or through the +\f2\i\fs18 +\f3\i0 HemizygousDominance +\f4\fs20 property.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s hemizygous dominance coefficient to some number \f3\fs18 x @@ -6489,7 +6496,12 @@ If you don\'92t care which subpopulation a mutation originated in, the \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait -\f4\fs20 .\ +\f4\fs20 . See also the +\f3\fs18 dominance +\f4\fs20 and +\f2\i\fs18 +\f3\i0 Dominance +\f4\fs20 properties.\ If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to \f3\fs18 NAN \f4\fs20 , as discussed in @@ -6524,7 +6536,12 @@ If the target mutation has been configured to exhibit independent dominance for \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effect sizes for a given target mutation will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait -\f4\fs20 .\ +\f4\fs20 . See also the +\f3\fs18 effectSize +\f4\fs20 and +\f2\i\fs18 +\f3\i0 EffectSize +\f4\fs20 properties.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ @@ -6545,7 +6562,12 @@ If the target mutation has been configured to exhibit independent dominance for \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait -\f4\fs20 .\ +\f4\fs20 . See also the +\f3\fs18 hemizygousDominance +\f4\fs20 and +\f2\i\fs18 +\f3\i0 HemizygousDominance +\f4\fs20 properties.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(logical)isIndependentDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ @@ -6587,7 +6609,10 @@ If the target mutation has been configured to exhibit independent dominance for \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL -\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. See also the +\f2\i\fs18 +\f3\i0 Dominance +\f4\fs20 property, which can be used to set the dominance of a mutation for a trait.\ The parameter \f3\fs18 dominance \f4\fs20 must follow one of four patterns. In the first pattern, @@ -6631,7 +6656,10 @@ A dominance value of \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL -\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. See also the +\f2\i\fs18 +\f3\i0 EffectSize +\f4\fs20 property, which can be used to set the effect size of a mutation for a trait.\ The parameter \f3\fs18 effectSize \f4\fs20 must follow one of four patterns. In the first pattern, @@ -6669,7 +6697,10 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 Trait \f4\fs20 objects; \f3\fs18 NULL -\f4\fs20 represents all of the traits in the species, in the order in which they were defined.\ +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. See also the +\f2\i\fs18 +\f3\i0 HemizygousDominance +\f4\fs20 property, which can be used to set the hemizygous dominance of a mutation for a trait.\ The parameter \f3\fs18 dominance \f4\fs20 must follow one of four patterns. In the first pattern, @@ -6725,7 +6756,7 @@ In nucleotide-based models, a restriction applies: nucleotide-based mutations ma \f3\fs18 color \f4\fs20 is the empty string, \f3\fs18 "" -\f4\fs20 , SLiMgui\'92s default (selection-coefficient\'96based) color scheme is used; this is the default for new +\f4\fs20 , SLiMgui\'92s default (effect-size\'96based) color scheme is used; this is the default for new \f3\fs18 MutationType \f4\fs20 objects.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -6743,7 +6774,7 @@ In nucleotide-based models, a restriction applies: nucleotide-based mutations ma \f3\fs18 colorSubstitution \f4\fs20 is the empty string, \f3\fs18 "" -\f4\fs20 , SLiMgui\'92s default (selection-coefficient\'96based) color scheme is used; this is the default for new +\f4\fs20 , SLiMgui\'92s default (effect-size\'96based) color scheme is used; this is the default for new \f3\fs18 MutationType \f4\fs20 objects.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -6940,9 +6971,9 @@ If a default dominance coefficient of \f4\fs20 method \f3\fs18 dominanceForTrait() \f4\fs20 regarding obtaining the realized dominance coefficient used in a particular mutation, given that mutation\'92s effect size for the trait.\ -Note that dominance coefficients are not bounded. A dominance coefficient greater than +Note that dominance coefficients are not bounded. For multiplicative traits, dominance coefficient greater than \f3\fs18 1.0 -\f4\fs20 may be used to achieve an overdominance effect. By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ +\f4\fs20 may be used to achieve an overdominance effect. By making the effect size very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision \f3\fs18 float \f4\fs20 , not the double-precision @@ -6968,9 +6999,6 @@ Also note that dominance coefficients have a quirk: they are stored internally i \f4\fs20 method \f3\fs18 setHemizygousDominanceForTrait() \f4\fs20 .\ -Note that dominance coefficients are not bounded. A dominance coefficient greater than -\f3\fs18 1.0 -\f4\fs20 may be used to achieve an overdominance effect. By making the selection coefficient very small and the dominance coefficient very large, an overdominance scenario in which both homozygotes have the same fitness may be approximated, to a nearly arbitrary degree of precision.\ Also note that dominance coefficients have a quirk: they are stored internally in SLiM as a single-precision \f3\fs18 float \f4\fs20 , not the double-precision @@ -9681,7 +9709,15 @@ The spatial periodicity of the simulation for this species, as specified in \f4\fs20 \cf2 The \f3\fs18 Trait -\f4\fs20 objects defined in the species. These are guaranteed to be in sorted order by their index (i.e., by the order in which they were defined).\ +\f4\fs20 objects defined in the species. These are guaranteed to be in sorted order by their index (i.e., by the order in which they were defined). The +\f3\fs18 traitsWithIndices() +\f4\fs20 and +\f3\fs18 traitsWithNames() +\f4\fs20 methods and the +\f3\fs18 +\f4\fs20 property are also useful for obtaining particular +\f3\fs18 Trait +\f4\fs20 objects from the species.\ \pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 \f1\i\fs22 \cf0 5.16.2 @@ -11489,7 +11525,11 @@ This method is shorthand for getting the \f3\fs18 indices \f4\fs20 does not correspond to a trait in the target species, an error will be raised. See also \f3\fs18 traitsWithNames() -\f4\fs20 .\ +\f4\fs20 , and the +\f3\fs18 traits +\f4\fs20 and +\f3\fs18 +\f4\fs20 properties.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(object)traitsWithNames(string\'a0names)\ @@ -11503,7 +11543,11 @@ This method is shorthand for getting the \f3\fs18 names \f4\fs20 does not correspond to a trait in the target species, an error will be raised. See also \f3\fs18 traitsWithIndices() -\f4\fs20 .\ +\f4\fs20 , and the +\f3\fs18 traits +\f4\fs20 and +\f3\fs18 +\f4\fs20 properties.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \expnd0\expndtw0\kerning0 @@ -14700,13 +14744,12 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 \cf2 The dominance coefficient(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the \f3\fs18 dominanceForTrait() -\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named -\f3\fs18 height -\f4\fs20 , for example, then +\f4\fs20 method. Also note that a dynamic property named +\f2\i\fs18 +\f3\i0 Dominance +\f4\fs20 is defined on \f3\fs18 Substitution -\f4\fs20 objects will have a dynamic property named -\f3\fs18 heightDominance -\f4\fs20 to access the dominance for that trait.\ +\f4\fs20 for each trait in the model, providing direct access to the dominance of a substitution for a specific trait.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 effectSize => (float)\ @@ -14714,13 +14757,12 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 \cf2 The effect size(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the effect sizes for all of the traits (in the order in which the traits were defined). For more control, see the \f3\fs18 effectSizeForTrait() -\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named -\f3\fs18 height -\f4\fs20 , for example, then +\f4\fs20 method. Also note that a dynamic property named +\f2\i\fs18 +\f3\i0 EffectSize +\f4\fs20 is defined on \f3\fs18 Substitution -\f4\fs20 objects will have a dynamic property named -\f3\fs18 heightEffectSize -\f4\fs20 to access the effect size for that trait.\ +\f4\fs20 for each trait in the model, providing direct access to the effect size of a substitution for a specific trait.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 hemizygousDominance => (float)\ @@ -14728,13 +14770,12 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 \cf2 The hemizygous dominance coefficient(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the \f3\fs18 hemizygousDominanceForTrait() -\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named -\f3\fs18 height -\f4\fs20 , for example, then +\f4\fs20 method. Also note that a dynamic property named +\f2\i\fs18 +\f3\i0 HemizygousDominance +\f4\fs20 is defined on \f3\fs18 Substitution -\f4\fs20 objects will have a dynamic property named -\f3\fs18 heightHemizygousDominance -\f4\fs20 to access the dominance for that trait.\ +\f4\fs20 for each trait in the model, providing direct access to the hemizygous dominance of a substitution for a specific trait.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 id => (integer$)\ @@ -14865,7 +14906,12 @@ Note that this method is only for use in nonWF models, in which migration is man \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait -\f4\fs20 . +\f4\fs20 . See also the +\f3\fs18 dominance +\f4\fs20 and +\f2\i\fs18 +\f3\i0 Dominance +\f4\fs20 properties. \f5\fs22 \cf0 \ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -14888,7 +14934,12 @@ Note that this method is only for use in nonWF models, in which migration is man \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Effect sizes for a given target substitution will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait -\f4\fs20 .\ +\f4\fs20 . See also the +\f3\fs18 effectSize +\f4\fs20 and +\f2\i\fs18 +\f3\i0 EffectSize +\f4\fs20 properties.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(float)hemizygousDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ @@ -14909,7 +14960,12 @@ Note that this method is only for use in nonWF models, in which migration is man \f3\fs18 NULL \f4\fs20 represents all of the traits in the species, in the order in which they were defined. Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by \f3\fs18 trait -\f4\fs20 .\ +\f4\fs20 . See also the +\f3\fs18 hemizygousDominance +\f4\fs20 and +\f2\i\fs18 +\f3\i0 HemizygousDominance +\f4\fs20 properties.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 \'96\'a0(logical)isIndependentDominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 6d1b4c1d..4a0c6350 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -908,7 +908,8 @@ The \f2\fs20 , but the \f1\fs18 setDefaultHemizygousDominanceForTrait() \f2\fs20 method can configure it subsequently if desired.\ -The +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 The \f1\fs18 distributionType \f2\fs20 and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits. The DES for the mutation type for a specific trait can subsequently be separately configured with the \f1\fs18 setEffectSizeDistributionForTrait() @@ -920,27 +921,27 @@ The \f1\fs18 ... \f2\fs20 should supply a \f1\fs18 numeric$ -\f2\fs20 fixed selection coefficient; +\f2\fs20 fixed effect size; \f1\fs18 "e" \f2\fs20 , in which case the ellipsis should supply a \f1\fs18 numeric$ -\f2\fs20 mean selection coefficient for an exponential distribution; +\f2\fs20 mean effect size for an exponential distribution; \f1\fs18 "g" \f2\fs20 , in which case the ellipsis should supply a \f1\fs18 numeric$ -\f2\fs20 mean selection coefficient and a +\f2\fs20 mean effect size and a \f1\fs18 numeric$ \f2\fs20 alpha shape parameter for a gamma distribution; \f1\fs18 "n" \f2\fs20 , in which case the ellipsis should supply a \f1\fs18 numeric$ -\f2\fs20 mean selection coefficient and a +\f2\fs20 mean effect size and a \f1\fs18 numeric$ \f2\fs20 sigma (standard deviation) parameter for a normal distribution; \f1\fs18 "p" \f2\fs20 , in which case the ellipsis should supply a \f1\fs18 numeric$ -\f2\fs20 mean selection coefficient and a +\f2\fs20 mean effect size and a \f1\fs18 numeric$ \f2\fs20 scale parameter for a Laplace distribution; \f1\fs18 "w" @@ -967,7 +968,8 @@ The \f2\fs20 is assumed and must not be supplied. See the \f1\fs18 MutationType \f2\fs20 class documentation for discussion of the various DESs and their uses.\ -\expnd0\expndtw0\kerning0 +\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 \expnd0\expndtw0\kerning0 Note that by default in WF models, all mutations of a given mutation type will be converted into \f1\fs18 Substitution \f2\fs20 objects when they reach fixation, for efficiency reasons. If you need to disable this conversion, to keep mutations of a given type active in the simulation even after they have fixed, you can do so by setting the From dfb4b9103272956d404cbc12b21175525811678e Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 24 Jan 2026 09:42:21 -0600 Subject: [PATCH 091/107] change short names for trait types to "m" and "a" --- QtSLiM/help/SLiMHelpFunctions.html | 2 +- SLiMgui/SLiMHelpFunctions.rtf | 9 +++++---- core/slim_test_genetics.cpp | 14 +++++++------- core/species_eidos.cpp | 4 ++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 019a9717..aa31906c 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -147,7 +147,7 @@

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [logical$ directFitnessEffect = F])

Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the Individual, Species, Mutation, or Substitution classes.  These requirements are necessary because, after the new trait is created, new properties are added to those classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named height, getting and setting an individual’s trait value would be possible through the property individual.height.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.height would provide the Trait object named height.  See the Mutation and Substitution classes for details on the trait-related properties defined for them.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("height", height) would allow the Trait object to be referenced simply as height.

-

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).  The shorter versions "mul" and "add" are also allowed.

+

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).  The shorter versions "m" and "a" are also allowed.

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive traits, such that the baseline offset has no effect upon the trait value.  Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of 0.0.

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  As for the baseline offset, the individual offset mean defaults (if NULL is passed) to 1.0 for multiplicative traits, 0.0 for additive traits, to produce no effect.  The default standard deviation for the individual offset, if NULL is passed, is 0.0.  If NULL is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.  Note that for multiplicative traits all effects, including individual offsets, will be clamped to a minimum of 0.0.

Finally, the directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 4a0c6350..4a559a8e 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -908,8 +908,7 @@ The \f2\fs20 , but the \f1\fs18 setDefaultHemizygousDominanceForTrait() \f2\fs20 method can configure it subsequently if desired.\ -\pard\pardeftab543\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 The +The \f1\fs18 distributionType \f2\fs20 and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits. The DES for the mutation type for a specific trait can subsequently be separately configured with the \f1\fs18 setEffectSizeDistributionForTrait() @@ -1351,6 +1350,7 @@ If \f1\fs18 initializeChromosome() \f2\fs20 , allowing a different mutation run count to be specified for each chromosome in multi-chromosome models.\expnd0\expndtw0\kerning0 \ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 \kerning1\expnd0\expndtw0 If \f1\fs18 preventIncidentalSelfing \f2\fs20 is @@ -1430,6 +1430,7 @@ If \f2\fs20 for \f1\fs18 checkInfiniteLoops \f2\fs20 to disable these checks. There is no way to turn these checks on or off for individual loops; it is a global setting.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \cf0 This function will likely be extended with further options in the future, added on to the end of the argument list. Using named arguments with this call is recommended for readability. Note that turning on optional features may increase the runtime and memory footprint of SLiM.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -1548,9 +1549,9 @@ The \f2\fs20 , if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or \f1\fs18 "additive" \f2\fs20 , if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model). The shorter versions -\f1\fs18 "mul" +\f1\fs18 "m" \f2\fs20 and -\f1\fs18 "add" +\f1\fs18 "a" \f2\fs20 are also allowed.\ The \f1\fs18 baselineOffset diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index df648320..ffed70b4 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1709,15 +1709,15 @@ initialize() { initializeSex(); // multiplicative traits - popgen1T = initializeTrait("popgen1T", "mul", 1.01, 1.0, 0.01, directFitnessEffect=T); // will have a mix of dominance - popgen2T = initializeTrait("popgen2T", "mul", 1.01, 1.0, 0.01, directFitnessEffect=T); // will be independent dominance - n1T = initializeTrait("n1T", "mul", NULL, NULL, NULL, directFitnessEffect=T); // neutral with direct effect - n2T = initializeTrait("n2T", "mul", NULL, NULL, NULL, directFitnessEffect=F); // neutral with no direct effect + popgen1T = initializeTrait("popgen1T", "m", 1.01, 1.0, 0.01, directFitnessEffect=T); // will have a mix of dominance + popgen2T = initializeTrait("popgen2T", "m", 1.01, 1.0, 0.01, directFitnessEffect=T); // will be independent dominance + n1T = initializeTrait("n1T", "m", NULL, NULL, NULL, directFitnessEffect=T); // neutral with direct effect + n2T = initializeTrait("n2T", "m", NULL, NULL, NULL, directFitnessEffect=F); // neutral with no direct effect // additive traits - quant1T = initializeTrait("quant1T", "add", I1, 0.0, 0.01, directFitnessEffect=F); // will have a mix of dominance - quant2T = initializeTrait("quant2T", "add", I2, 0.0, 0.01, directFitnessEffect=F); // will be independent dominance - n3T = initializeTrait("n3T", "add", NULL, NULL, NULL, directFitnessEffect=F); // non-neutral with no direct effect + quant1T = initializeTrait("quant1T", "a", I1, 0.0, 0.01, directFitnessEffect=F); // will have a mix of dominance + quant2T = initializeTrait("quant2T", "a", I2, 0.0, 0.01, directFitnessEffect=F); // will be independent dominance + n3T = initializeTrait("n3T", "a", NULL, NULL, NULL, directFitnessEffect=F); // non-neutral with no direct effect // quant1T / quant2T will be demanded in script; popgen1T / popgen2T / n1T will be demanded because they have direct effects // calculation of popgen2T and quant2T should be extremely efficient since they are independent dominance diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 7ea7ea5a..10889f6c 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1681,9 +1681,9 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string std::string type_string = type_value->StringAtIndex_NOCAST(0, nullptr); TraitType type; - if ((type_string == gStr_multiplicative) || (type_string == "mul")) + if ((type_string == gStr_multiplicative) || (type_string == "m")) type = TraitType::kMultiplicative; - else if ((type_string == gStr_additive) || (type_string == "add")) + else if ((type_string == gStr_additive) || (type_string == "a")) type = TraitType::kAdditive; else EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires type to be either 'multiplicative' (or 'mul'), or 'additive' ('add')." << EidosTerminate(); From c6f9889c9ed0fd95c388b5557de25f282b55c649 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 24 Jan 2026 11:38:50 -0600 Subject: [PATCH 092/107] switch to lognormal distribution for multiplicative individual offsets --- QtSLiM/help/SLiMHelpClasses.html | 4 +-- QtSLiM/help/SLiMHelpFunctions.html | 4 +-- SLiMgui/SLiMHelpClasses.rtf | 25 ++++++++++------ SLiMgui/SLiMHelpFunctions.rtf | 43 ++++++++++++---------------- VERSIONS | 1 + core/community_eidos.cpp | 2 +- core/slim_test_genetics.cpp | 34 +++++++++++----------- core/species_eidos.cpp | 26 ++++------------- core/trait.cpp | 46 +++++++++++++++++++----------- core/trait.h | 12 +++++--- 10 files changed, 102 insertions(+), 95 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 8342b7b7..956c90ed 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -1448,9 +1448,9 @@

index => (integer$)

The index of the trait in the vector of traits kept by the species.  The first trait defined in a species is at index 0, and subsequent traits count upwards from there.  The index of a trait is often used to refer to the trait, so it is important.  A global constant is defined for every trait, using each trait’s name, that provides the index of each trait, so this property will probably rarely be needed.

individualOffsetMean <–> (float$)

-

The mean for the normal distribution from which individual offsets are drawn.  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetSD property.

+

The mean for the normal distribution from which individual offsets are drawn.  (As described in initializeTrait(), for multiplicative traits the drawn values are transformed with exp() before use, so in effect a lognormal distribution is used, and individualOffsetMean is the mean of that lognormal distribution in log space.)  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetSD property.

individualOffsetSD <–> (float$)

-

The standard deviation for the normal distribution from which individual offsets are drawn.  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetMean property.

+

The standard deviation for the normal distribution from which individual offsets are drawn.  (As described in initializeTrait(), for multiplicative traits the drawn values are transformed with exp() before use, so in effect a lognormal distribution is used, and individualOffsetSD is the standard deviation of that lognormal distribution in log space.)  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetMean property.

name => (string$)

The name of the trait, as given to initializeTrait().  The default trait that is provided if no trait is explicitly defined has a name that is the name of the species plus a T; so for a single-species model, the default trait will generally be named simT.  The name of a trait can sometimes be used to refer to the trait, and is visible in SLiMgui.

species => (object<Species>$)

diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index aa31906c..7b86ba20 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -144,12 +144,12 @@

The tickModulo and tickPhase parameters determine the activation schedule for the species.  The active property of the species will be set to T (thus activating the species) every tickModulo ticks, beginning in tick tickPhase.  (However, when the species is activated in a given tick, the skipTick() method may still be called in a first() event to deactivate it.)  See the active property of Species for more details.

The avatar parameter, if not "", sets a string value used to represent the species graphically, particularly in SLiMgui but perhaps in other contexts also.  The avatar should generally be a single character – usually an emoji corresponding to the species, such as "🦊" for foxes or "🐭" for mice.  If avatar is the empty string, "", SLiMgui will choose a default avatar.

The color parameter, if not "", sets a string color value used to represent the species in SLiMgui.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB" (see the Eidos manual for details).  If color is the empty string, "", SLiMgui will choose a default color.

-

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [logical$ directFitnessEffect = F])

+

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [float$ individualOffsetMean = 0.0], [float$ individualOffsetSD = 0.0], [logical$ directFitnessEffect = F])

Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the Individual, Species, Mutation, or Substitution classes.  These requirements are necessary because, after the new trait is created, new properties are added to those classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named height, getting and setting an individual’s trait value would be possible through the property individual.height.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.height would provide the Trait object named height.  See the Mutation and Substitution classes for details on the trait-related properties defined for them.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("height", height) would allow the Trait object to be referenced simply as height.

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).  The shorter versions "m" and "a" are also allowed.

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive traits, such that the baseline offset has no effect upon the trait value.  Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of 0.0.

-

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  As for the baseline offset, the individual offset mean defaults (if NULL is passed) to 1.0 for multiplicative traits, 0.0 for additive traits, to produce no effect.  The default standard deviation for the individual offset, if NULL is passed, is 0.0.  If NULL is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not.  Note that for multiplicative traits all effects, including individual offsets, will be clamped to a minimum of 0.0.

+

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  For additive traits, these drawn values are used directly as individual offsets; for multiplicative traits, they are transformed with exp() before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale).  The default values for these parameters provide a zero-width individual offset distribution that produces no effect.  Note that individual offsets can be set on individuals with setOffsetForTrait() or the <trait-name>Offset property, both on class Individual, so you are not limited to this built-in mechanism for drawing individual offsets; it is just provided for convenience.

Finally, the directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

The use of the initializeTrait() function is optional.  If it is not called, a new Trait object will be created automatically, with a name generated from the species name plus a "T"; typically, then, the name is simT, except in multispecies models.  This default trait is configured to be multiplicative, with default values for the other parameters except directFitnessEffect, which is T for the default trait.  This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.2.  The creation of the default trait occurs as a side effect of the first call to initializeMutationType(), if initializeTrait() has not already been called.

(void)initializeTreeSeq([logical$ recordMutations = T], [Nif$ simplificationRatio = NULL], [Ni$ simplificationInterval = NULL], [logical$ checkCoalescence = F], [logical$ runCrosschecks = F], [logical$ retainCoalescentOnly = T], [Ns$ timeUnit = NULL])

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 4fbeaccd..c1b47939 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -6258,8 +6258,7 @@ You can get the \f2\i\fs18 \f3\i0 Dominance \f4\fs20 property.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to +If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to \f3\fs18 NAN \f4\fs20 , as discussed in \f3\fs18 initializeMutationType() @@ -6301,8 +6300,7 @@ Note that dominance coefficients in SLiM have a quirk: they are stored internall \f2\i\fs18 \f3\i0 EffectSize \f4\fs20 property.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +Note that effect sizes in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s effect size to some number \f3\fs18 x @@ -6329,8 +6327,7 @@ Note that dominance coefficients in SLiM have a quirk: they are stored internall \f2\i\fs18 \f3\i0 HemizygousDominance \f4\fs20 property.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation +Note that dominance coefficients in SLiM have a quirk: they are stored internally in SLiM using a single-precision float, not the double-precision float type normally used by Eidos. This means that if you set a mutation \f3\fs18 mut \f4\fs20 \'92s hemizygous dominance coefficient to some number \f3\fs18 x @@ -15036,7 +15033,13 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 \cf2 individualOffsetMean <\'96> (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The mean for the normal distribution from which individual offsets are drawn. Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the +\f4\fs20 \cf2 The mean for the normal distribution from which individual offsets are drawn. (As described in +\f3\fs18 initializeTrait() +\f4\fs20 , for multiplicative traits the drawn values are transformed with +\f3\fs18 exp() +\f4\fs20 before use, so in effect a lognormal distribution is used, and +\f3\fs18 individualOffsetMean +\f4\fs20 is the mean of that lognormal distribution in log space.) Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the \f3\fs18 individualOffsetSD \f4\fs20 property.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -15044,7 +15047,13 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 \cf2 individualOffsetSD <\'96> (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The standard deviation for the normal distribution from which individual offsets are drawn. Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the +\f4\fs20 \cf2 The standard deviation for the normal distribution from which individual offsets are drawn. (As described in +\f3\fs18 initializeTrait() +\f4\fs20 , for multiplicative traits the drawn values are transformed with +\f3\fs18 exp() +\f4\fs20 before use, so in effect a lognormal distribution is used, and +\f3\fs18 individualOffsetSD +\f4\fs20 is the standard deviation of that lognormal distribution in log space.) Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the \f3\fs18 individualOffsetMean \f4\fs20 property.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 4a559a8e..d0e2f871 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -1,7 +1,7 @@ {\rtf1\ansi\ansicpg1252\cocoartf2761 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Optima-Bold;\f1\fnil\fcharset0 Menlo-Regular;\f2\fswiss\fcharset0 Optima-Regular; \f3\fswiss\fcharset0 Optima-Italic;\f4\froman\fcharset0 TimesNewRomanPSMT;\f5\fnil\fcharset0 AppleColorEmoji; -\f6\froman\fcharset0 TimesNewRomanPS-ItalicMT;} +\f6\fnil\fcharset0 Menlo-Italic;\f7\froman\fcharset0 TimesNewRomanPS-ItalicMT;} {\colortbl;\red255\green255\blue255;\red0\green0\blue0;} {\*\expandedcolortbl;;\cssrgb\c0\c0\c0;} \margl1440\margr1440\vieww9000\viewh8400\viewkind0 @@ -1495,7 +1495,7 @@ The \f2\fs20 , SLiMgui will choose a default color.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf2 (object$)initializeTrait(string$\'a0name, string$\'a0type, [Nf$\'a0baselineOffset\'a0=\'a0NULL], [Nf$\'a0individualOffsetMean\'a0=\'a0NULL], [Nf$\'a0individualOffsetSD\'a0=\'a0NULL], [logical$\'a0directFitnessEffect\'a0=\'a0F])\ +\f1\fs18 \cf2 (object$)initializeTrait(string$\'a0name, string$\'a0type, [Nf$\'a0baselineOffset\'a0=\'a0NULL], [float$\'a0individualOffsetMean\'a0=\'a00.0], [float$\'a0individualOffsetSD\'a0=\'a00.0], [logical$\'a0directFitnessEffect\'a0=\'a0F])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f2\fs20 \cf2 Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized. The new @@ -1568,21 +1568,16 @@ The \f1\fs18 individualOffsetMean \f2\fs20 and \f1\fs18 individualOffsetSD -\f2\fs20 parameters together define a normal distribution from which individual offsets are drawn to provide what is often called \'93environmental variance\'94 or \'93developmental noise\'94. As for the baseline offset, the individual offset mean defaults (if -\f1\fs18 NULL -\f2\fs20 is passed) to -\f1\fs18 1.0 -\f2\fs20 for multiplicative traits, -\f1\fs18 0.0 -\f2\fs20 for additive traits, to produce no effect. The default standard deviation for the individual offset, if -\f1\fs18 NULL -\f2\fs20 is passed, is -\f1\fs18 0.0 -\f2\fs20 . If -\f1\fs18 NULL -\f2\fs20 is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not. Note that for multiplicative traits all effects, including individual offsets, will be clamped to a minimum of -\f1\fs18 0.0 -\f2\fs20 .\ +\f2\fs20 parameters together define a normal distribution from which individual offsets are drawn to provide what is often called \'93environmental variance\'94 or \'93developmental noise\'94. For additive traits, these drawn values are used directly as individual offsets; for multiplicative traits, they are transformed with +\f1\fs18 exp() +\f2\fs20 before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale). The default values for these parameters provide a zero-width individual offset distribution that produces no effect. Note that individual offsets can be set on individuals with +\f1\fs18 setOffsetForTrait() +\f2\fs20 or the +\f6\i\fs18 +\f1\i0 Offset +\f2\fs20 property, both on class +\f1\fs18 Individual +\f2\fs20 , so you are not limited to this built-in mechanism for drawing individual offsets; it is just provided for convenience.\ Finally, the \f1\fs18 directFitnessEffect \f2\fs20 parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual. This will typically be @@ -2811,11 +2806,11 @@ The implementation \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 \f2\fs20 \cf2 Calculates -\f6\i \uc0\u960 +\f7\i \uc0\u960 \f2\i0 (nucleotide diversity, a metric of genetic diversity) for a vector of haplosomes (containing at least two elements), based upon the mutations in the haplosomes. -\f6\i \uc0\u960 +\f7\i \uc0\u960 \f2\i0 is computed by calculating the mean number of pairwise differences at each site, summing across all sites, and dividing by the number of sites. Therefore, it is interpretable as the number of differences per site expected between two randomly chosen sequences. The mathematical formulation (as an estimator of the population parameter -\f6\i \uc0\u952 +\f7\i \uc0\u952 \f2\i0 ) is based on work in Nei and Li (1979), Nei and Tajima (1981), and Tajima (1983; equation A3). The exact formula used here is common in textbooks (e.g., equations 9.1\'969.5 in Li 1997, equation 3.3 in Hahn 2018, or equation 2.2 in Coop 2020).\ Often \f1\fs18 haplosomes @@ -2835,7 +2830,7 @@ The calculation can be narrowed to apply to only a window \'96 a subrange of the \f2\fs20 of \f1\fs18 NULL \f2\fs20 , provides the haplosome-wide value of -\f6\i \uc0\u960 +\f7\i \uc0\u960 \f2\i0 .\ The implementation of \f1\fs18 calcPi() @@ -2844,7 +2839,7 @@ The implementation of \f2\fs20 , treats every mutation as independent in the heterozygosity calculations. One could regard this choice as embodying an infinite-sites interpretation of the segregating mutations, as with \f1\fs18 calcHeterozygosity() \f2\fs20 . Indeed, finite-sites models of -\f6\i \uc0\u960 +\f7\i \uc0\u960 \f2\i0 have been derived (Tajima 1996) though are not used here. In most biologically realistic models, such genetic states will be quite rare, and so the impact of this assumption will be negligible; however, in some models this distinction may be important. See \f1\fs18 calcPairHeterozygosity() \f2\fs20 for further discussion. This function was written by Nick Bailey (currently affiliated with CNRS and the Laboratory of Biometry and Evolutionary Biology at University Lyon 1), with helpful input from Peter Ralph and Chase Nelson.\ @@ -3015,9 +3010,9 @@ The implementation of \f2\fs20 . Indeed, Tajima\'92s \f3\i D \f2\i0 can be modified with finite-sites models of -\f6\i \uc0\u960 +\f7\i \uc0\u960 \f2\i0 and -\f6\i \uc0\u952 +\f7\i \uc0\u952 \f2\i0 (Misawa and Tajima 1997) though these are not used here. In most biologically realistic models, such genetic states will be quite rare, and so the impact of this assumption will be negligible; however, in some models this distinction may be important. See \f1\fs18 calcPairHeterozygosity() \f2\fs20 for further discussion. This function was written by Nick Bailey (currently affiliated with CNRS and the Laboratory of Biometry and Evolutionary Biology at University Lyon 1), with helpful input from Peter Ralph.\ diff --git a/VERSIONS b/VERSIONS index d9d1868e..98971a27 100644 --- a/VERSIONS +++ b/VERSIONS @@ -164,6 +164,7 @@ multitrait branch: setEffectSizeForTrait() parameter "effect" -> "effectSize" MutationType's loggedData() parameter "effect" -> "effectSize" and lots of related fallout for loggedData() and logMutationData(), logged_effect_, running_effect_, etc. for initializeMutationType() and initializeMutationTypeNuc(), change the "dominanceCoeff" parameter to "defaultDominance" + switch over to using a lognormal distribution for the default individual offset distribution for multiplicative traits version 5.1 (Eidos version 4.1): diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index ea7ad311..62b36b0f 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -125,7 +125,7 @@ const std::vector *Community::ZeroTickFunctionSignat ->AddIntString_S("id")->AddNumeric_S("defaultDominance")->AddString_OSN("distributionType", gStaticEidosValueNULL)->AddEllipsis()); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeRecombinationRate, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric("rates")->AddInt_ON("ends", gStaticEidosValueNULL)->AddString_OS("sex", gStaticEidosValue_StringAsterisk)); - sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeTrait, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class, "SLiM"))->AddString_S("name")->AddString_S("type")->AddFloat_OSN("baselineOffset", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetMean", gStaticEidosValueNULL)->AddFloat_OSN("individualOffsetSD", gStaticEidosValueNULL)->AddLogical_OS("directFitnessEffect", gStaticEidosValue_LogicalF)); + sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeTrait, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class, "SLiM"))->AddString_S("name")->AddString_S("type")->AddFloat_OSN("baselineOffset", gStaticEidosValueNULL)->AddFloat_OS("individualOffsetMean", gStaticEidosValue_Float0)->AddFloat_OS("individualOffsetSD", gStaticEidosValue_Float0)->AddLogical_OS("directFitnessEffect", gStaticEidosValue_LogicalF)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeChromosome, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class, "SLiM"))->AddInt_S("id")->AddInt_OSN("length", gStaticEidosValueNULL)->AddString_OS("type", gStaticEidosValue_StringA)->AddString_OSN("symbol", gStaticEidosValueNULL)->AddString_OSN("name", gStaticEidosValueNULL)->AddInt_OS("mutationRuns", gStaticEidosValue_Integer0)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeGeneConversion, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric_S("nonCrossoverFraction")->AddNumeric_S("meanLength")->AddNumeric_S("simpleConversionFraction")->AddNumeric_OS("bias", gStaticEidosValue_Integer0)->AddLogical_OS("redrawLengthsOnFailure", gStaticEidosValue_LogicalF)); diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index ffed70b4..7311d4c4 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1318,19 +1318,19 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(0, INF); }", "default dominance is infinite", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultHemizygousDominanceForTrait(0, NAN); }", "hemizygous dominance is non-finite", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5); m1.setDefaultHemizygousDominanceForTrait(0, INF); }", "hemizygous dominance is non-finite", __LINE__); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); if (m1.defaultDominanceForTrait('A') == 0.5) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); if (isNAN(m1.defaultDominanceForTrait('A'))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); if (identical(m1.defaultDominanceForTrait(), c(0.5,0.5))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); if (identical(m1.defaultDominanceForTrait(), c(NAN,NAN))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(), c(0.25, 0.75))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('B','A'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(), c(0.75, 0.25))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('B','A'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(c('B','A')), c(0.25, 0.75))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(NAN, NAN)); if (identical(m1.defaultDominanceForTrait(), c(NAN, NAN))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A'), NAN); if (identical(m1.defaultDominanceForTrait(), c(NAN, 0.5))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); if (identical(m1.defaultDominanceForTrait(), c(0.5, NAN))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, 0.5)); if (identical(m1.defaultDominanceForTrait(), c(0.5, 0.5))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A'), 0.5); if (identical(m1.defaultDominanceForTrait(), c(0.5, NAN))) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('A', 'mul'); initializeTrait('B', 'mul'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); if (identical(m1.defaultDominanceForTrait(), c(0.5, NAN))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', 0.5); if (m1.defaultDominanceForTrait('A') == 0.5) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', NAN); if (isNAN(m1.defaultDominanceForTrait('A'))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', 0.5); if (identical(m1.defaultDominanceForTrait(), c(0.5,0.5))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', NAN); if (identical(m1.defaultDominanceForTrait(), c(NAN,NAN))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(), c(0.25, 0.75))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('B','A'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(), c(0.75, 0.25))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('B','A'), c(0.25, 0.75)); if (identical(m1.defaultDominanceForTrait(c('B','A')), c(0.25, 0.75))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(NAN, NAN)); if (identical(m1.defaultDominanceForTrait(), c(NAN, NAN))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A'), NAN); if (identical(m1.defaultDominanceForTrait(), c(NAN, 0.5))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', 0.5); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); if (identical(m1.defaultDominanceForTrait(), c(0.5, NAN))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, 0.5)); if (identical(m1.defaultDominanceForTrait(), c(0.5, 0.5))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A'), 0.5); if (identical(m1.defaultDominanceForTrait(), c(0.5, NAN))) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('A', 'm'); initializeTrait('B', 'm'); initializeMutationType('m1', NAN); m1.setDefaultDominanceForTrait(c('A','B'), c(0.5, NAN)); if (identical(m1.defaultDominanceForTrait(), c(0.5, NAN))) stop(); }"); std::string middle = " initializeGenomicElementType('g1', m1, 1.0); initializeGenomicElement(g1, 0, 99999); initializeRecombinationRate(1e-8); initializeMutationRate(1e-4); } 1 late() { sim.addSubpop('p1', 10); } 2 late() { muts = sim.mutations; "; @@ -1352,11 +1352,11 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "if (allClose(muts.effectSize, 0.0001)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.setDominanceForTrait(0, 0.5); if (all(muts.dominance == 0.5)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.heightDominance = 0.5; if (all(muts.dominance == 0.5)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.heightDominance = 0.5; if (all(muts.heightDominance == 0.5)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('height', 'm'); initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.heightDominance = 0.5; if (all(muts.dominance == 0.5)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('height', 'm'); initializeMutationType('m1', NAN, 'f', 0.0001);" + middle + "muts.heightDominance = 0.5; if (all(muts.heightDominance == 0.5)) stop(); }"); SLiMAssertScriptStop("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.setDominanceForTrait(0, NAN); if (allClose(muts.dominance, 0.4999875)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.heightDominance = NAN; if (allClose(muts.dominance, 0.4999875)) stop(); }"); - SLiMAssertScriptStop("initialize() { initializeTrait('height', 'mul'); initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.heightDominance = NAN; if (allClose(muts.heightDominance, 0.4999875)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('height', 'm'); initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.heightDominance = NAN; if (allClose(muts.dominance, 0.4999875)) stop(); }"); + SLiMAssertScriptStop("initialize() { initializeTrait('height', 'm'); initializeMutationType('m1', 0.5, 'f', 0.0001);" + middle + "muts.heightDominance = NAN; if (allClose(muts.heightDominance, 0.4999875)) stop(); }"); // Test the new Individual cachedFitness property and crosscheck it against the Subpopulation cachedFitness() method std::string cachedFitness1 = // neutral but with fitnessScaling diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 10889f6c..e3d6841b 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1628,7 +1628,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeSpecies(const std::strin return gStaticEidosValueVOID; } -// ********************* (object$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [Nf$ individualOffsetMean = NULL], [Nf$ individualOffsetSD = NULL], [l$ directFitnessEffect = F]) +// ********************* (object$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [f$ individualOffsetMean = 0.0], [f$ individualOffsetSD = 0.0], [l$ directFitnessEffect = F]) // EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -1686,7 +1686,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string else if ((type_string == gStr_additive) || (type_string == "a")) type = TraitType::kAdditive; else - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires type to be either 'multiplicative' (or 'mul'), or 'additive' ('add')." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires type to be either 'multiplicative' (or 'm'), or 'additive' (or 'a')." << EidosTerminate(); // baselineOffset slim_effect_t baselineOffset; @@ -1711,31 +1711,17 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string if ((type == TraitType::kMultiplicative) && (baselineOffset < (slim_effect_t)0.0)) baselineOffset = (slim_effect_t)0.0; - // check that the default distribution is used or not used, in its entirety - if (individualOffsetMean_value->Type() != individualOffsetSD_value->Type()) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that both individual offset parameters be NULL (to use the default distribution) or be non-NULL (to specify a distribution)." << EidosTerminate(); - // individualOffsetMean - double individualOffsetMean; - - if (individualOffsetMean_value->Type() == EidosValueType::kValueNULL) - individualOffsetMean = (type == TraitType::kMultiplicative) ? 1.0 : 0.0; - else - individualOffsetMean = individualOffsetMean_value->FloatAtIndex_NOCAST(0, nullptr); + double individualOffsetMean = individualOffsetMean_value->FloatAtIndex_NOCAST(0, nullptr); if (!std::isfinite(individualOffsetMean)) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires individualOffsetMean to be a finite value (not NAN or INF)." << EidosTerminate(); // individualOffsetSD - double individualOffsetSD; - - if (individualOffsetSD_value->Type() == EidosValueType::kValueNULL) - individualOffsetSD = 0.0; - else - individualOffsetSD = individualOffsetSD_value->FloatAtIndex_NOCAST(0, nullptr); + double individualOffsetSD = individualOffsetSD_value->FloatAtIndex_NOCAST(0, nullptr); - if (!std::isfinite(individualOffsetSD)) - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires individualOffsetSD to be a finite value (not NAN or INF)." << EidosTerminate(); + if ((!std::isfinite(individualOffsetSD)) || (individualOffsetSD < 0.0)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires individualOffsetSD to be a nonnegative finite value (not NAN or INF)." << EidosTerminate(); // directFitnessEffect bool directFitnessEffect = directFitnessEffect_value->LogicalAtIndex_NOCAST(0, nullptr); diff --git a/core/trait.cpp b/core/trait.cpp index 2ab213ea..be3a1be0 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -21,8 +21,8 @@ Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, sl EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property baselineOffset requires a finite value (not NAN or INF)." << EidosTerminate(); if (!std::isfinite(individualOffsetMean_)) EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property individualOffsetMean requires a finite value (not NAN or INF)." << EidosTerminate(); - if (!std::isfinite(individualOffsetSD_)) - EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property individualOffsetSD requires a finite value (not NAN or INF)." << EidosTerminate(); + if (!std::isfinite(individualOffsetSD_) || (individualOffsetSD_ < 0.0)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property individualOffsetSD requires a nonnegative finite value (not NAN or INF)." << EidosTerminate(); // effects for multiplicative traits clip at 0.0 if ((type_ == TraitType::kMultiplicative) && (p_baselineOffset < (slim_effect_t)0.0)) @@ -40,13 +40,17 @@ void Trait::_RecacheIndividualOffsetDistribution(void) { individualOffsetFixed_ = true; - // effects for multiplicative traits clip at 0.0 - slim_effect_t offset = static_cast(individualOffsetMean_); - - if ((type_ == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) - individualOffsetFixedValue_ = 0.0; + if (type_ == TraitType::kMultiplicative) + { + // multiplicative traits use an exp() transformation to get a lognormal distribution + // (effects for multiplicative traits also clip at 0.0, but exp() guarantees that anyway) + individualOffsetFixedValue_ = static_cast(std::exp(individualOffsetMean_)); + } else - individualOffsetFixedValue_ = offset; + { + // other traits use a normal distribution, so the mean is the mean + individualOffsetFixedValue_ = static_cast(individualOffsetMean_); + } } else { @@ -75,13 +79,21 @@ slim_effect_t Trait::_DrawIndividualOffset(void) const // note the individualOffsetSD_ == 0 case was already handled by DrawIndividualOffset() gsl_rng *rng = EIDOS_GSL_RNG(omp_get_thread_num()); - slim_effect_t offset = static_cast(gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_); - - // effects for multiplicative traits clip at 0.0 - if ((type_ == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) - offset = 0.0; - - return offset; + if (type_ == TraitType::kMultiplicative) + { + // multiplicative traits use an exp() transformation to get a lognormal distribution + // (effects for multiplicative traits also clip at 0.0, but exp() guarantees that anyway) + double normal_draw = gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_; + + return static_cast(std::exp(normal_draw)); + } + else + { + // other traits use a normal distribution, so the mean is the mean + double normal_draw = gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_; + + return static_cast(normal_draw); + } } EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) @@ -201,8 +213,8 @@ void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_v { double value = p_value.FloatAtIndex_NOCAST(0, nullptr); - if (!std::isfinite(value)) - EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property individualOffsetSD requires a finite value (not NAN or INF)." << EidosTerminate(); + if (!std::isfinite(value) || (value < 0.0)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property individualOffsetSD requires a nonnegative finite value (not NAN or INF)." << EidosTerminate(); individualOffsetSD_ = value; _RecacheIndividualOffsetDistribution(); diff --git a/core/trait.h b/core/trait.h index f7a98715..1ab9f64d 100644 --- a/core/trait.h +++ b/core/trait.h @@ -58,14 +58,18 @@ class Trait : public EidosDictionaryRetained std::string name_; // the user-visible name of this trait TraitType type_; // multiplicative or additive - // offsets + // baseline offset, added to the trait value of every individual slim_effect_t baselineOffset_; - bool individualOffsetFixed_; // true if individualOffsetSD_ == 0.0 - slim_effect_t individualOffsetFixedValue_; // equal to individualOffsetMean_ if individualOffsetFixed_ == true; pre-cast for speed + // default individual offset distribution parameters, used to generate per-individual offsets double individualOffsetMean_; double individualOffsetSD_; + // an optimization for the individual offset distribution, caching a fixed offset value if individualOffsetSD_ + // is 0.0; note that the cached fixed value here includes the exp() transform for multiplicative traits + bool individualOffsetFixed_; // true if individualOffsetSD_ == 0.0 + slim_effect_t individualOffsetFixedValue_; // pre-calculated and pre-cast for speed + // if true, the calculated trait value is used directly as a fitness effect, automatically // this mimics the previous behavior of SLiM, for multiplicative traits bool directFitnessEffect_; @@ -130,7 +134,7 @@ class Trait : public EidosDictionaryRetained slim_effect_t BaselineOffset(void) const { return baselineOffset_; }; void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetFixed_ and individualOffsetFixedValue_ - slim_effect_t _DrawIndividualOffset(void) const; // draws from a normal distribution defined by individualOffsetMean_ and individualOffsetSD_ + slim_effect_t _DrawIndividualOffset(void) const; // draws from the distribution defined by individualOffsetMean_ and individualOffsetSD_ inline __attribute__((always_inline)) slim_effect_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } inline __attribute__((always_inline)) bool HasDirectFitnessEffect(void) const { return directFitnessEffect_; } From 4c50171be64223b53919bec781d9626a4721aee9 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 24 Jan 2026 12:18:37 -0600 Subject: [PATCH 093/107] fix lots of CI self-test errors --- core/slim_test_genetics.cpp | 24 +++++++++++------------- core/species.cpp | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 7311d4c4..aefad896 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1038,10 +1038,8 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', baselineOffset=NAN); }", "baselineOffset to be a finite value", __LINE__); SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=INF, individualOffsetSD=0.0); }", "individualOffsetMean to be a finite value", __LINE__); SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=NAN, individualOffsetSD=0.0); }", "individualOffsetMean to be a finite value", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=1.0, individualOffsetSD=INF); }", "individualOffsetSD to be a finite value", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=1.0, individualOffsetSD=NAN); }", "individualOffsetSD to be a finite value", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=2.0, individualOffsetSD=NULL); }", "individual offset parameters be", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=NULL, individualOffsetSD=2.0); }", "individual offset parameters be", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=1.0, individualOffsetSD=INF); }", "individualOffsetSD to be a nonnegative finite value", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative', individualOffsetMean=1.0, individualOffsetSD=NAN); }", "individualOffsetSD to be a nonnegative finite value", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0); initializeTrait('height', 'multiplicative'); }", "already been implicitly defined", __LINE__); SLiMAssertScriptRaise("initialize() { initializeTrait('height', 'multiplicative'); initializeMutationType('m1', 0.5, 'f', 0.0); initializeTrait('weight', 'multiplicative'); }", "before a mutation type is created", __LINE__); SLiMAssertScriptRaise("initialize() { for (i in 1:257) initializeTrait('height' + i, 'multiplicative'); }", "maximum number of traits", __LINE__); @@ -1076,7 +1074,7 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.index, 0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.index, 1)) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.individualOffsetMean, 1.0)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.individualOffsetMean, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.individualOffsetMean, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; if (!identical(T_height.individualOffsetMean, 3.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; if (!identical(T_weight.individualOffsetMean, 2.5)) stop(); }"); @@ -1087,8 +1085,8 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.individualOffsetSD, 0.0)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetSD = 3.5; if (!identical(T_height.individualOffsetSD, 3.5)) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetSD = 2.5; if (!identical(T_weight.individualOffsetSD, 2.5)) stop(); }"); - SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.individualOffsetSD = NAN; }", "requires a finite value", __LINE__); - SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.individualOffsetSD = INF; }", "requires a finite value", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.individualOffsetSD = NAN; }", "requires a nonnegative finite value", __LINE__); + SLiMAssertScriptRaise(mt_base_p1 + "1 late() { T_height.individualOffsetSD = INF; }", "requires a nonnegative finite value", __LINE__); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_height.name, 'height')) stop(); }"); SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(T_weight.name, 'weight')) stop(); }"); @@ -1122,7 +1120,7 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, INF); }", "offset values to be finite", __LINE__); SLiMAssertScriptRaise(mt_base_p1 + "1 late() { p1.individuals.setOffsetForTrait(0, c(1,INF,3,INF,5)); }", "offset values to be finite", __LINE__); - SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_height), rep(3.5, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_height.individualOffsetMean = 3.5; } 2 late() { if (!allClose(p1.individuals.offsetForTrait(T_height), rep(exp(3.5), 5))) stop(); }"); // note exp() post-transform SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { T_weight.individualOffsetMean = 2.5; } 2 late() { if (!identical(p1.individuals.offsetForTrait(T_weight), rep(2.5, 5))) stop(); }"); // individual phenotype @@ -1709,15 +1707,15 @@ initialize() { initializeSex(); // multiplicative traits - popgen1T = initializeTrait("popgen1T", "m", 1.01, 1.0, 0.01, directFitnessEffect=T); // will have a mix of dominance - popgen2T = initializeTrait("popgen2T", "m", 1.01, 1.0, 0.01, directFitnessEffect=T); // will be independent dominance - n1T = initializeTrait("n1T", "m", NULL, NULL, NULL, directFitnessEffect=T); // neutral with direct effect - n2T = initializeTrait("n2T", "m", NULL, NULL, NULL, directFitnessEffect=F); // neutral with no direct effect + popgen1T = initializeTrait("popgen1T", "m", 1.01, 0.0, 0.01, directFitnessEffect=T); // will have a mix of dominance + popgen2T = initializeTrait("popgen2T", "m", 1.01, 0.0, 0.01, directFitnessEffect=T); // will be independent dominance + n1T = initializeTrait("n1T", "m", directFitnessEffect=T); // neutral with direct effect + n2T = initializeTrait("n2T", "m", directFitnessEffect=F); // neutral with no direct effect // additive traits quant1T = initializeTrait("quant1T", "a", I1, 0.0, 0.01, directFitnessEffect=F); // will have a mix of dominance quant2T = initializeTrait("quant2T", "a", I2, 0.0, 0.01, directFitnessEffect=F); // will be independent dominance - n3T = initializeTrait("n3T", "a", NULL, NULL, NULL, directFitnessEffect=F); // non-neutral with no direct effect + n3T = initializeTrait("n3T", "a", directFitnessEffect=F); // non-neutral with no direct effect // quant1T / quant2T will be demanded in script; popgen1T / popgen2T / n1T will be demanded because they have direct effects // calculation of popgen2T and quant2T should be extremely efficient since they are independent dominance diff --git a/core/species.cpp b/core/species.cpp index 3a0126c0..b1177298 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1454,7 +1454,7 @@ void Species::MakeImplicitTrait(void) // Mirroring SLiM versions prior to multi-trait support, the implicit trait is a multiplicative trait with // no baselines (1.0, since it is multiplicative) and a direct effect from phenotype on fitness. std::string trait_name = name_ + "T"; - Trait *trait = new Trait(*this, trait_name, TraitType::kMultiplicative, 1.0, 1.0, 0.0, true); + Trait *trait = new Trait(*this, trait_name, TraitType::kMultiplicative, 1.0, 0.0, 0.0, true); // Add it to our registry; AddTrait() takes its retain count AddTrait(trait); From 987aac874ee0464f511b132b436680338d685103 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 24 Jan 2026 17:45:34 -0600 Subject: [PATCH 094/107] fix column name in loggedData() --- core/mutation_type.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 18a43222..54696a75 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -1267,7 +1267,7 @@ EidosValue_SP MutationType::ExecuteMethod_loggedData(EidosGlobalStringID p_metho for (size_t log_index = 0; log_index < log_size_; log_index++) column_data[log_index] = (double)trait_log.logged_effect_size_[log_index]; } - dataframe->SetKeyValue_StringKeys(trait_name + "Effect", EidosValue_SP(column)); + dataframe->SetKeyValue_StringKeys(trait_name + "EffectSize", EidosValue_SP(column)); } if (get_dominance) From 70852889aa1aee19f5978ed4f3b4505fc7e049d9 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 24 Jan 2026 17:47:27 -0600 Subject: [PATCH 095/107] add new "logistic" trait type --- QtSLiM/help/SLiMHelpClasses.html | 22 ++++---- QtSLiM/help/SLiMHelpFunctions.html | 6 +- SLiMgui/SLiMHelpClasses.rtf | 26 +++++---- SLiMgui/SLiMHelpFunctions.rtf | 20 ++++--- VERSIONS | 1 + core/individual.cpp | 90 +++++++++++++++++++++++++++--- core/mutation.cpp | 2 +- core/slim_globals.cpp | 25 +-------- core/slim_globals.h | 9 ++- core/species.cpp | 8 ++- core/species_eidos.cpp | 16 +++++- core/substitution.cpp | 2 +- core/trait.cpp | 15 +++-- core/trait.h | 15 +++-- 14 files changed, 173 insertions(+), 84 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 956c90ed..b8776e46 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -737,12 +737,12 @@

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.

5.10.2  Mutation methods

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

-

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.  See also the dominance and <trait-value>Dominance properties.

+

Returns the mutation’s dominance coefficient for the trait(s) specified by trait; for all trait types this is used as the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.  See also the dominance and <trait-value>Dominance properties.

If the target mutation has been configured to exhibit independent dominance for a given trait by setting its dominance value for that trait to NAN, as discussed in initializeMutationType() and setDominanceForTrait(), this method does not return that value of NAN; instead, it returns the dominance value that will actually be used by SLiM to implement independent dominance, referred to as the “realized” dominance value (see isIndependentDominanceForTrait() for the way to determine whether independent dominance is configured for a trait).  This realized dominance value depends upon the mutation’s corresponding effect size, and may change if that effect size changes.  The class Trait documentation provides further discussion of independent dominance.

– (float)effectSizeForTrait([Niso<Trait> trait = NULL])

-

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.  See also the effectSize and <trait-value>EffectSize properties.

+

Returns the mutation’s effect size for the trait(s) specified by trait; for multiplicative traits, this is typically the selection coefficient s, whereas for additive and logistic traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.  See also the effectSize and <trait-value>EffectSize properties.

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

-

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.  See also the hemizygousDominance and <trait-value>HemizygousDominance properties.

+

Returns the mutation’s hemizygous dominance coefficient for the trait(s) specified by trait; for all trait types this is used as the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.  See also the hemizygousDominance and <trait-value>HemizygousDominance properties.

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

Returns whether the mutation is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A mutation is configured for independent dominance for a trait if it inherited a default dominance of NAN for the trait from its mutation type, or if its dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation for discussion of this feature.  Independent dominance flags for a given target mutation will be returned consecutively in the order in which the traits are specified by trait.

+ (void)setDominanceForTrait([Niso<Trait> trait = NULL], [Nif dominance = NULL])

@@ -808,7 +808,7 @@

Starts or ends logging of data about new mutations belonging to the target mutation type.  If autogeneratedOnly is T (the default), only new mutations generated automatically by SLiM will be logged (including mutations that are substituted in for an auto-generated mutation using a mutation() callback; that is still considered part of the auto-generation process).  If autogeneratedOnly is F, mutations generated in script, such as with addNewMutation(), addNewDrawnMutation(), and reading from files such as VCF, MS, or .trees, will also be logged.  The logged information can be obtained later with the loggedData() method.  Once logging has been started with enable=T it cannot be modified, only stopped with enable=F; and if logging is subsequently resumed with enable=T, any previously logged data will be discarded.  (This can be useful if you wish to limit the size of the in-memory data while continuing to log new data: periodically write the accumulated data to a file and then disable and re-enable logging to discard the old data.)

If meanOnly is F (the default), values for each new mutation will be kept separately.  Beware: the memory usage entailed by this option can be extremely large!  Alternatively, if meanOnly is T, only a running sum, used to compute a mean, will be kept for each type of data; the memory usage for this option will be small and constant, but of course a mean is more useful for some columns of data than others.  If per-mutation data is desired for any one column, use meanOnly=F; this option cannot be controlled independently for the various columns of data being logged.

Next are parameters that control which mutation properties will be logged: id controls the id property; mutationTypeID controls the id property of the mutation’s mutation type (this will be the same for all mutations logged by a given MutationType, it can be useful if you combine datasets from more than one mutation type later); chromosomeID controls the id property of the mutation’s associated chromosome; position controls the position property; nucleotideValue controls the nucleotideValue property; originTick controls the originTick property; subpopID controls the subpopID property; and tag controls the tag property.  Data columns will be added in the order of these parameters; the id column will be first, if requested, for example.

-

Last come parameters that control the logging of trait-associated data for the new mutations.  The trait parameter controls which traits will be logged.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  For each specified trait, the effectSize parameter controls logging of the effect size, the dominance parameter controls the dominance, and the hemizygousDominance parameter controls the hemizygous dominance.  Data columns for this trait-associated data will be grouped by trait; for example, if two traits named height and weight are specified, and the effectSize=T and dominance=T flags are specified, then columns heightEffect, heightDominance, weightEffect, and weightDominance will be added, in that order.

+

Last come parameters that control the logging of trait-associated data for the new mutations.  The trait parameter controls which traits will be logged.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  For each specified trait, the effectSize parameter controls logging of the effect size, the dominance parameter controls the dominance, and the hemizygousDominance parameter controls the hemizygous dominance.  Data columns for this trait-associated data will be grouped by trait; for example, if two traits named height and weight are specified, and the effectSize=T and dominance=T flags are specified, then columns heightEffectSize, heightDominance, weightEffectSize, and weightDominance will be added, in that order.

Note that logging occurs after all mutation() callbacks have been called, at the point when the new mutation is actually added to the simulation.  If addition of a new mutation is prevented, by a mutation() callback or by the current stacking policy, that mutation will not be logged.  The information logged will be the mutation’s properties at the moment that it is added; a tag value set by a mutation() callback will therefore be captured in the logged data, for example.  Changes to mutations made after that point will not be preserved in the log; the log is a snapshot of the moment of each mutation’s addition to the simulation.

– (void)setDefaultDominanceForTrait(Niso<Trait> trait, float dominance)

Set the default dominance coefficient for a specified trait or traits, for the target mutation type.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species.  The value of dominance must either be singleton (in which case it is set as the default dominance for all specified traits), or must match the number of specified traits (in which case one element of defaultDominance is used for each corresponding trait).

@@ -1431,26 +1431,26 @@

A user-defined integer value.  The value of tag is carried over automatically from the original Mutation object.  Apart from that, the value of tag is not used by SLiM; it is free for you to use.

5.18.2  Substitution methods

– (float)dominanceForTrait([Niso<Trait> trait = NULL])

-

Returns the substitution’s dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.  See also the dominance and <trait-value>Dominance properties.

+

Returns the substitution’s dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For all trait types this is used as the dominance coefficient h.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.  See also the dominance and <trait-value>Dominance properties.

– (float)effectSizeForTrait([Niso<Trait> trait = NULL])

-

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.  See also the effectSize and <trait-value>EffectSize properties.

+

Returns the substitution’s effect size for the trait(s) specified by trait, carried over from the original mutation object.  For multiplicative traits, this is typically the selection coefficient s, whereas for additive and logistic traits it is typically the additive effect size a.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Effect sizes for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.  See also the effectSize and <trait-value>EffectSize properties.

– (float)hemizygousDominanceForTrait([Niso<Trait> trait = NULL])

-

Returns the substitution’s hemizygous dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For both multiplicative traits and additive traits this is the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.  See also the hemizygousDominance and <trait-value>HemizygousDominance properties.

+

Returns the substitution’s hemizygous dominance coefficient for the trait(s) specified by trait, carried over from the original mutation object.  For all trait types this is used as the hemizygous dominance coefficient hhemi.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  Hemizygous dominance coefficients for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.  See also the hemizygousDominance and <trait-value>HemizygousDominance properties.

– (logical)isIndependentDominanceForTrait([Niso<Trait> trait = NULL])

Returns whether the substitution is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A substitution is configured for independent dominance for a trait if the original mutation inherited a default dominance of NAN for the trait from its mutation type, or if the original mutation’s dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation for discussion of this feature.  Independent dominance flags for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

5.19  Class Trait

5.19.1  Trait properties

baselineOffset <–> (float$)

-

The baseline offset for the trait.  This value is combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.

+

The baseline offset for the trait.  This value is combined – multiplicatively for multiplicative traits, additively for additive and logistic traits – with all other effects that influence the trait.  This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  A baseline offset value set through this property may therefore not be the value used by SLiM or subsequently returned by this property.

directFitnessEffect => (logical$)

A logical flag indicating whether the trait has a direct fitness effect or not.  If T, the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component.  If F, the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a “fitness function”), or the trait might have other effects that are not obviously related to fitness at all.

index => (integer$)

The index of the trait in the vector of traits kept by the species.  The first trait defined in a species is at index 0, and subsequent traits count upwards from there.  The index of a trait is often used to refer to the trait, so it is important.  A global constant is defined for every trait, using each trait’s name, that provides the index of each trait, so this property will probably rarely be needed.

individualOffsetMean <–> (float$)

-

The mean for the normal distribution from which individual offsets are drawn.  (As described in initializeTrait(), for multiplicative traits the drawn values are transformed with exp() before use, so in effect a lognormal distribution is used, and individualOffsetMean is the mean of that lognormal distribution in log space.)  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetSD property.

+

The mean for the normal distribution from which individual offsets are drawn.  (As described in initializeTrait(), for multiplicative traits the drawn values are transformed with exp() before use, so in effect a lognormal distribution is used, and individualOffsetMean is the mean of that lognormal distribution in log space.)  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive and logistic traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetSD property.

individualOffsetSD <–> (float$)

-

The standard deviation for the normal distribution from which individual offsets are drawn.  (As described in initializeTrait(), for multiplicative traits the drawn values are transformed with exp() before use, so in effect a lognormal distribution is used, and individualOffsetSD is the standard deviation of that lognormal distribution in log space.)  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetMean property.

+

The standard deviation for the normal distribution from which individual offsets are drawn.  (As described in initializeTrait(), for multiplicative traits the drawn values are transformed with exp() before use, so in effect a lognormal distribution is used, and individualOffsetSD is the standard deviation of that lognormal distribution in log space.)  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive and logistic traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetMean property.

name => (string$)

The name of the trait, as given to initializeTrait().  The default trait that is provided if no trait is explicitly defined has a name that is the name of the species plus a T; so for a single-species model, the default trait will generally be named simT.  The name of a trait can sometimes be used to refer to the trait, and is visible in SLiMgui.

species => (object<Species>$)

@@ -1458,7 +1458,7 @@

tag <–> (integer$)

A user-defined integer value.  The value of tag is initially undefined, and it is an error to try to read it; if you wish it to have a defined value, you must arrange that yourself by explicitly setting its value prior to using it elsewhere in your code.  The value of tag is not used by SLiM; it is free for you to use.

type => (string$)

-

The type of the trait, as a string.  In the present design, this will be either "multiplicative" or "additive".

+

The type of the trait, as a string.  In the present design, this will be either "multiplicative", "additive", or "logistic".

5.19.2  Trait methods


diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 7b86ba20..0fd9f26c 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -147,9 +147,9 @@

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [float$ individualOffsetMean = 0.0], [float$ individualOffsetSD = 0.0], [logical$ directFitnessEffect = F])

Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the Individual, Species, Mutation, or Substitution classes.  These requirements are necessary because, after the new trait is created, new properties are added to those classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named height, getting and setting an individual’s trait value would be possible through the property individual.height.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.height would provide the Trait object named height.  See the Mutation and Substitution classes for details on the trait-related properties defined for them.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("height", height) would allow the Trait object to be referenced simply as height.

-

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model).  The shorter versions "m" and "a" are also allowed.

-

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive traits, such that the baseline offset has no effect upon the trait value.  Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of 0.0.

-

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  For additive traits, these drawn values are used directly as individual offsets; for multiplicative traits, they are transformed with exp() before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale).  The default values for these parameters provide a zero-width individual offset distribution that produces no effect.  Note that individual offsets can be set on individuals with setOffsetForTrait() or the <trait-name>Offset property, both on class Individual, so you are not limited to this built-in mechanism for drawing individual offsets; it is just provided for convenience.

+

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model); "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model); or "logistic", if the trait value should be the result of a logistic transformation of an additive trait value (for modeling a trait that represents a probability, such as a disease risk).  (Because the logistic trait type is based upon an underlying additive trait value that is transformed, it will often be grouped together in this manual as having “additive” effects; this is in reference to that underlying model, prior to the logistic transformation.)  The shorter versions "m", "a", and "l" are also allowed.

+

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive and logistic traits, such that the baseline offset has no effect upon the trait value.  Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of 0.0.

+

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  For additive traits, these drawn values are used directly as individual offsets; for multiplicative traits, they are transformed with exp() before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale); and for logistic traits, they are used directly as individual offsets for the underlying additive trait.  The default values for these parameters provide a zero-width individual offset distribution that produces no effect.  Note that individual offsets can be set on individuals with setOffsetForTrait() or the <trait-name>Offset property, both on class Individual, so you are not limited to this built-in mechanism for drawing individual offsets; it is just provided for convenience.

Finally, the directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

The use of the initializeTrait() function is optional.  If it is not called, a new Trait object will be created automatically, with a name generated from the species name plus a "T"; typically, then, the name is simT, except in multispecies models.  This default trait is configured to be multiplicative, with default values for the other parameters except directFitnessEffect, which is T for the default trait.  This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.2.  The creation of the default trait occurs as a side effect of the first call to initializeMutationType(), if initializeTrait() has not already been called.

(void)initializeTreeSeq([logical$ recordMutations = T], [Nif$ simplificationRatio = NULL], [Ni$ simplificationInterval = NULL], [logical$ checkCoalescence = F], [logical$ runCrosschecks = F], [logical$ retainCoalescentOnly = T], [Ns$ timeUnit = NULL])

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index c1b47939..72b3113c 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -6481,7 +6481,7 @@ If you don\'92t care which subpopulation a mutation originated in, the \f4\fs20 \cf2 Returns the mutation\'92s dominance coefficient for the trait(s) specified by \f3\fs18 trait -\f4\fs20 ; for both multiplicative traits and additive traits this is the dominance coefficient +\f4\fs20 ; for all trait types this is used as the dominance coefficient \f1\i h \f4\i0 . The traits can be specified as \f3\fs18 integer @@ -6521,7 +6521,7 @@ If the target mutation has been configured to exhibit independent dominance for \f3\fs18 trait \f4\fs20 ; for multiplicative traits, this is typically the selection coefficient \f1\i s -\f4\i0 , whereas for additive traits it is typically the additive effect size +\f4\i0 , whereas for additive and logistic traits it is typically the additive effect size \f1\i a \f4\i0 . The traits can be specified as \f3\fs18 integer @@ -6546,7 +6546,7 @@ If the target mutation has been configured to exhibit independent dominance for \f4\fs20 \cf2 Returns the mutation\'92s hemizygous dominance coefficient for the trait(s) specified by \f3\fs18 trait -\f4\fs20 ; for both multiplicative traits and additive traits this is the hemizygous dominance coefficient +\f4\fs20 ; for all trait types this is used as the hemizygous dominance coefficient \f1\i h \f4\i0\fs13\fsmilli6667 \sub hemi \fs20 \nosupersub . The traits can be specified as @@ -7227,11 +7227,11 @@ Last come parameters that control the logging of trait-associated data for the n \f4\fs20 and \f3\fs18 dominance=T \f4\fs20 flags are specified, then columns -\f3\fs18 heightEffect +\f3\fs18 heightEffectSize \f4\fs20 , \f3\fs18 heightDominance \f4\fs20 , -\f3\fs18 weightEffect +\f3\fs18 weightEffectSize \f4\fs20 , and \f3\fs18 weightDominance \f4\fs20 will be added, in that order.\ @@ -14891,7 +14891,7 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 \cf2 Returns the substitution\'92s dominance coefficient for the trait(s) specified by \f3\fs18 trait -\f4\fs20 , carried over from the original mutation object. For both multiplicative traits and additive traits this is the dominance coefficient +\f4\fs20 , carried over from the original mutation object. For all trait types this is used as the dominance coefficient \f1\i h \f4\i0 . The traits can be specified as \f3\fs18 integer @@ -14919,7 +14919,7 @@ Note that this method is only for use in nonWF models, in which migration is man \f3\fs18 trait \f4\fs20 , carried over from the original mutation object. For multiplicative traits, this is typically the selection coefficient \f1\i s -\f4\i0 , whereas for additive traits it is typically the additive effect size +\f4\i0 , whereas for additive and logistic traits it is typically the additive effect size \f1\i a \f4\i0 . The traits can be specified as \f3\fs18 integer @@ -14944,7 +14944,7 @@ Note that this method is only for use in nonWF models, in which migration is man \f4\fs20 \cf2 Returns the substitution\'92s hemizygous dominance coefficient for the trait(s) specified by \f3\fs18 trait -\f4\fs20 , carried over from the original mutation object. For both multiplicative traits and additive traits this is the hemizygous dominance coefficient +\f4\fs20 , carried over from the original mutation object. For all trait types this is used as the hemizygous dominance coefficient \f1\i h \f4\i0\fs13\fsmilli6667 \sub hemi \fs20 \nosupersub . The traits can be specified as @@ -15002,7 +15002,7 @@ Note that this method is only for use in nonWF models, in which migration is man \f3\i0\fs18 \cf2 baselineOffset <\'96> (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf2 The baseline offset for the trait. This value is combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.\ +\f4\fs20 \cf2 The baseline offset for the trait. This value is combined \'96 multiplicatively for multiplicative traits, additively for additive and logistic traits \'96 with all other effects that influence the trait. This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.\ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 0.0 \f4\fs20 as documented in the @@ -15039,7 +15039,7 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 exp() \f4\fs20 before use, so in effect a lognormal distribution is used, and \f3\fs18 individualOffsetMean -\f4\fs20 is the mean of that lognormal distribution in log space.) Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the +\f4\fs20 is the mean of that lognormal distribution in log space.) Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive and logistic traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the \f3\fs18 individualOffsetSD \f4\fs20 property.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -15053,7 +15053,7 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 exp() \f4\fs20 before use, so in effect a lognormal distribution is used, and \f3\fs18 individualOffsetSD -\f4\fs20 is the standard deviation of that lognormal distribution in log space.) Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the +\f4\fs20 is the standard deviation of that lognormal distribution in log space.) Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive and logistic traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the \f3\fs18 individualOffsetMean \f4\fs20 property.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 @@ -15095,8 +15095,10 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 string \f4\fs20 . In the present design, this will be either \f3\fs18 "multiplicative" -\f4\fs20 or +\f4\fs20 , \f3\fs18 "additive" +\f4\fs20 , or +\f3\fs18 "logistic" \f4\fs20 .\ \pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index d0e2f871..197737b6 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -1540,18 +1540,23 @@ The \f2\fs20 object to be referenced simply as \f1\fs18 height \f2\fs20 .\ -The +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 The \f1\fs18 type \f2\fs20 parameter gives the type of trait to be created, as a \f1\fs18 string \f2\fs20 value. This should be either \f1\fs18 "multiplicative" -\f2\fs20 , if the trait value should be the result of multiplying effects together (as in a typical population-genetics model), or +\f2\fs20 , if the trait value should be the result of multiplying effects together (as in a typical population-genetics model); \f1\fs18 "additive" -\f2\fs20 , if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model). The shorter versions +\f2\fs20 , if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model); or +\f1\fs18 "logistic" +\f2\fs20 , if the trait value should be the result of a logistic transformation of an additive trait value (for modeling a trait that represents a probability, such as a disease risk). (Because the logistic trait type is based upon an underlying additive trait value that is transformed, it will often be grouped together in this manual as having \'93additive\'94 effects; this is in reference to that underlying model, prior to the logistic transformation.) The shorter versions \f1\fs18 "m" -\f2\fs20 and +\f2\fs20 , \f1\fs18 "a" +\f2\fs20 , and +\f1\fs18 "l" \f2\fs20 are also allowed.\ The \f1\fs18 baselineOffset @@ -1561,7 +1566,7 @@ The \f1\fs18 1.0 \f2\fs20 for multiplicative traits, \f1\fs18 0.0 -\f2\fs20 for additive traits, such that the baseline offset has no effect upon the trait value. Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of +\f2\fs20 for additive and logistic traits, such that the baseline offset has no effect upon the trait value. Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of \f1\fs18 0.0 \f2\fs20 .\ The @@ -1570,7 +1575,7 @@ The \f1\fs18 individualOffsetSD \f2\fs20 parameters together define a normal distribution from which individual offsets are drawn to provide what is often called \'93environmental variance\'94 or \'93developmental noise\'94. For additive traits, these drawn values are used directly as individual offsets; for multiplicative traits, they are transformed with \f1\fs18 exp() -\f2\fs20 before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale). The default values for these parameters provide a zero-width individual offset distribution that produces no effect. Note that individual offsets can be set on individuals with +\f2\fs20 before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale); and for logistic traits, they are used directly as individual offsets for the underlying additive trait. The default values for these parameters provide a zero-width individual offset distribution that produces no effect. Note that individual offsets can be set on individuals with \f1\fs18 setOffsetForTrait() \f2\fs20 or the \f6\i\fs18 @@ -1578,7 +1583,8 @@ The \f2\fs20 property, both on class \f1\fs18 Individual \f2\fs20 , so you are not limited to this built-in mechanism for drawing individual offsets; it is just provided for convenience.\ -Finally, the +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 Finally, the \f1\fs18 directFitnessEffect \f2\fs20 parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual. This will typically be \f1\fs18 T diff --git a/VERSIONS b/VERSIONS index 98971a27..ec3fe5f7 100644 --- a/VERSIONS +++ b/VERSIONS @@ -165,6 +165,7 @@ multitrait branch: MutationType's loggedData() parameter "effect" -> "effectSize" and lots of related fallout for loggedData() and logMutationData(), logged_effect_, running_effect_, etc. for initializeMutationType() and initializeMutationTypeNuc(), change the "dominanceCoeff" parameter to "defaultDominance" switch over to using a lognormal distribution for the default individual offset distribution for multiplicative traits + add a third trait type, "logistic" or "l", for modeling traits that represent a probability such as a disease risk version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index c76d53a2..c5a2f431 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -6695,13 +6695,33 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv if (traitType == TraitType::kAdditive) { - for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + if (trait->HasLogisticPostTransform()) { - Individual *ind = individuals_buffer[individual_index]; - IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; - - if (f_force_recalc || std::isnan(trait_info.phenotype_)) - trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + // logistic trait, post-process calculated values + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; + + if (f_force_recalc || std::isnan(trait_info.phenotype_)) + { + double additive_result = (double)(trait_baseline_offset + trait_info.offset_); + + trait_info.phenotype_ = static_cast(1.0 / (1.0 + std::exp(- static_cast(additive_result)))); + } + } + } + else + { + // regular additive trait + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; + + if (f_force_recalc || std::isnan(trait_info.phenotype_)) + trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + } } } else // (traitType == TraitType::kMultiplicative) @@ -7195,6 +7215,31 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual haplosome_index += chromosome->IntrinsicPloidy(); } + // post-transformation for logistic traits; this has to be done after we finish processing + // all traits for all chromosomes, since the outer loop above is over chromosomes + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; + + if (trait->HasLogisticPostTransform()) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + slim_effect_t &phenotype_ref = ind->trait_info_[trait_index].phenotype_; + double additive_result = (double)phenotype_ref; + + if (std::isfinite(additive_result)) + phenotype_ref = static_cast(1.0 / (1.0 + std::exp(-additive_result))); + } + } + } + // clear out each subpopulation's per-trait caches that we set up above; these are only for our private use for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { @@ -7593,6 +7638,31 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s haplosome_index += chromosome->IntrinsicPloidy(); } + // post-transformation for logistic traits; this has to be done after we finish processing + // all traits for all chromosomes, since the outer loop above is over chromosomes + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + Trait *trait = species->Traits()[trait_index]; + + if (trait->HasLogisticPostTransform()) + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) + continue; + + slim_effect_t &phenotype_ref = ind->trait_info_[trait_index].phenotype_; + double additive_result = (double)phenotype_ref; + + if (std::isfinite(additive_result)) + phenotype_ref = static_cast(1.0 / (1.0 + std::exp(-additive_result))); + } + } + } + #if DEBUG // Do a check of all computed results, against the same things computed by brute force. This will // probably be quite expensive, so probably I can't leave it enabled all the time even in DEBUG. @@ -8237,8 +8307,14 @@ slim_effect_t Individual::_CheckPhenotypeForTrait(slim_trait_index_t trait_index haplosome_index += chromosome->IntrinsicPloidy(); } - // finally, restore our saved phenotype so we don't modify the official individual state + // get the calculated trait value back out trait_value = trait_info.phenotype_; + + // post-process logistic trait values + if (trait->HasLogisticPostTransform()) + trait_value = static_cast(1.0 / (1.0 + std::exp(- static_cast(trait_value)))); + + // finally, restore our saved phenotype so we don't modify the official individual state trait_info.phenotype_ = saved_phenotype; return trait_value; diff --git a/core/mutation.cpp b/core/mutation.cpp index fdac216d..1f9f2a28 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -544,7 +544,7 @@ slim_effect_t Mutation::RealizedDominanceForTrait(Trait *p_trait) const // NAN indicates independent dominance and needs to be handled specially here if (p_trait->Type() == TraitType::kAdditive) { - // for additive traits independent dominance is always 0.5 + // for additive and logistic traits independent dominance is always 0.5 return 0.5; } else diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index 1bed3ac2..b025a9bc 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -653,30 +653,6 @@ std::string StringForSLiMCycleStage(SLiMCycleStage p_stage) } // stream output for enumerations -std::string StringForTraitType(TraitType p_trait_type) -{ - switch (p_trait_type) - { - case TraitType::kAdditive: return gStr_additive; - case TraitType::kMultiplicative: return gStr_multiplicative; - } - EIDOS_TERMINATION << "ERROR (StringForTraitType): (internal error) unexpected p_trait_type value." << EidosTerminate(); -} - -TraitType TraitTypeForString(std::string type) -{ - if (type == gStr_additive) return TraitType::kAdditive; - else if (type == gStr_multiplicative) return TraitType::kMultiplicative; - else - EIDOS_TERMINATION << "ERROR (TraitTypeForString): unrecognized triat type '" << type << "'." << EidosTerminate(); -} - -std::ostream& operator<<(std::ostream& p_out, TraitType p_trait_type) -{ - p_out << StringForTraitType(p_trait_type); - return p_out; -} - std::string StringForChromosomeType(ChromosomeType p_chromosome_type) { switch (p_chromosome_type) @@ -1659,6 +1635,7 @@ const std::string &gStr_context = EidosRegisteredString("context", gID_context); // mostly other fixed strings const std::string gStr_additive = "additive"; // these trait type strings are not registered, no need const std::string gStr_multiplicative = "multiplicative"; +const std::string gStr_logistic = "logistic"; const std::string gStr_A = "A"; // these nucleotide strings are not registered, no need const std::string gStr_C = "C"; const std::string gStr_G = "G"; diff --git a/core/slim_globals.h b/core/slim_globals.h index 067c0c9f..f19a1ad8 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -588,16 +588,14 @@ enum class SLiMCycleStage std::string StringForSLiMCycleStage(SLiMCycleStage p_stage); -// This enumeration represents the type of a trait: multiplicative or additive. +// This enumeration represents the type of a trait: multiplicative or additive. Note that at the Eidos API level +// we also support logistic traits, but that is not considered a trait type internally; it is implemented as a +// post-transformation of an additive trait, and controlled by a separate flag in Trait. enum class TraitType : uint8_t { kMultiplicative = 0, kAdditive }; -std::string StringForTraitType(TraitType p_trait_type); -TraitType TraitTypeForString(std::string type); // raises if no match -std::ostream& operator<<(std::ostream& p_out, TraitType p_trait_type); - // This enumeration represents the type of a chromosome. Note that the sex of an individual cannot always be inferred // from chromosomal state, and the user is allowed to play games with null haplosomes; the chromosomes follow the sex // of the individual, the sex of the individual does not follow the chromosomes. See the initializeChromosome() doc. @@ -1236,6 +1234,7 @@ extern const std::string &gStr_context; extern const std::string gStr_additive; // these trait type strings are not registered, no need extern const std::string gStr_multiplicative; +extern const std::string gStr_logistic; extern const std::string gStr_A; // these nucleotide strings are not registered, no need extern const std::string gStr_C; extern const std::string gStr_G; diff --git a/core/species.cpp b/core/species.cpp index b1177298..a9a701b6 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1454,7 +1454,13 @@ void Species::MakeImplicitTrait(void) // Mirroring SLiM versions prior to multi-trait support, the implicit trait is a multiplicative trait with // no baselines (1.0, since it is multiplicative) and a direct effect from phenotype on fitness. std::string trait_name = name_ + "T"; - Trait *trait = new Trait(*this, trait_name, TraitType::kMultiplicative, 1.0, 0.0, 0.0, true); + Trait *trait = new Trait(*this, trait_name, + /* p_type */ TraitType::kMultiplicative, + /* p_logistic_post */ false, + /* p_baselineOffset */ 1.0, + /* p_individualOffsetMean */ 0.0, + /* p_individualOffsetSD */ 0.0, + /* directFitnessEffect */ true); // Add it to our registry; AddTrait() takes its retain count AddTrait(trait); diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index e3d6841b..44ec687b 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1680,13 +1680,25 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string // type std::string type_string = type_value->StringAtIndex_NOCAST(0, nullptr); TraitType type; + bool logistic_post; if ((type_string == gStr_multiplicative) || (type_string == "m")) + { type = TraitType::kMultiplicative; + logistic_post = false; + } else if ((type_string == gStr_additive) || (type_string == "a")) + { type = TraitType::kAdditive; + logistic_post = false; + } + else if ((type_string == gStr_logistic) || (type_string == "l")) + { + type = TraitType::kAdditive; + logistic_post = true; + } else - EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires type to be either 'multiplicative' (or 'm'), or 'additive' (or 'a')." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires type to be either 'multiplicative' (or 'm'), 'additive' (or 'a'), or 'logistic' (or 'l')." << EidosTerminate(); // baselineOffset slim_effect_t baselineOffset; @@ -1727,7 +1739,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string bool directFitnessEffect = directFitnessEffect_value->LogicalAtIndex_NOCAST(0, nullptr); // Set up the new trait object; it gets a retain count on it from EidosDictionaryRetained::EidosDictionaryRetained() - Trait *trait = new Trait(*this, name, type, baselineOffset, individualOffsetMean, individualOffsetSD, directFitnessEffect); + Trait *trait = new Trait(*this, name, type, logistic_post, baselineOffset, individualOffsetMean, individualOffsetSD, directFitnessEffect); EidosValue_SP result_SP = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(trait, gSLiM_Trait_Class)); // Add it to our registry; AddTrait() takes its retain count diff --git a/core/substitution.cpp b/core/substitution.cpp index 9cc97b9d..f05c0b21 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -196,7 +196,7 @@ slim_effect_t Substitution::RealizedDominanceForTrait(Trait *p_trait) // NAN indicates independent dominance and needs to be handled specially here if (p_trait->Type() == TraitType::kAdditive) { - // for additive traits independent dominance is always 0.5 + // for additive and logistic traits independent dominance is always 0.5 return 0.5; } else diff --git a/core/trait.cpp b/core/trait.cpp index be3a1be0..4d8c33b7 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -11,8 +11,8 @@ #include "species.h" -Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, slim_effect_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect) : - index_(-1), name_(p_name), type_(p_type), +Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_effect_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect) : + index_(-1), name_(p_name), type_(p_type), logistic_post_(p_logistic_post), individualOffsetMean_(p_individualOffsetMean), individualOffsetSD_(p_individualOffsetSD), directFitnessEffect_(p_directFitnessEffect), community_(p_species.community_), species_(p_species) { @@ -24,6 +24,9 @@ Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, sl if (!std::isfinite(individualOffsetSD_) || (individualOffsetSD_ < 0.0)) EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property individualOffsetSD requires a nonnegative finite value (not NAN or INF)." << EidosTerminate(); + if (p_logistic_post && (type_ != TraitType::kAdditive)) + EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) logistic post-transformation is only supported for additive traits." << EidosTerminate(); + // effects for multiplicative traits clip at 0.0 if ((type_ == TraitType::kMultiplicative) && (p_baselineOffset < (slim_effect_t)0.0)) baselineOffset_ = 0.0; @@ -48,7 +51,7 @@ void Trait::_RecacheIndividualOffsetDistribution(void) } else { - // other traits use a normal distribution, so the mean is the mean + // additive and logistic traits use a normal distribution, so the mean is the mean individualOffsetFixedValue_ = static_cast(individualOffsetMean_); } } @@ -89,7 +92,7 @@ slim_effect_t Trait::_DrawIndividualOffset(void) const } else { - // other traits use a normal distribution, so the mean is the mean + // additive and logistic traits use a normal distribution, so the mean is the mean double normal_draw = gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_; return static_cast(normal_draw); @@ -118,6 +121,7 @@ EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) { static EidosValue_SP static_type_string_multiplicative; static EidosValue_SP static_type_string_additive; + static EidosValue_SP static_type_string_logistic; // FIXME PARALLEL static string allocation like this should be done at startup, before we go multithreaded; this should not need a critical section // search for "static EidosValue_SP" and fix all of them @@ -127,13 +131,14 @@ EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) { static_type_string_multiplicative = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_multiplicative)); static_type_string_additive = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_additive)); + static_type_string_logistic = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String(gStr_logistic)); } } switch (type_) { case TraitType::kMultiplicative: return static_type_string_multiplicative; - case TraitType::kAdditive: return static_type_string_additive; + case TraitType::kAdditive: return (logistic_post_ ? static_type_string_logistic : static_type_string_additive); default: return gStaticEidosValueNULL; // never hit; here to make the compiler happy } } diff --git a/core/trait.h b/core/trait.h index 1ab9f64d..b66f9530 100644 --- a/core/trait.h +++ b/core/trait.h @@ -56,7 +56,11 @@ class Trait : public EidosDictionaryRetained slim_trait_index_t index_; // the index of this trait within its species std::string name_; // the user-visible name of this trait - TraitType type_; // multiplicative or additive + + // Trait type: at the user level, multiplicative / additive / logistic, but logistic is treated + // internally here as additive with a logistic post-transformation controlled by a separate flag. + TraitType type_; + bool logistic_post_; // baseline offset, added to the trait value of every individual slim_effect_t baselineOffset_; @@ -123,13 +127,14 @@ class Trait : public EidosDictionaryRetained Trait& operator=(const Trait&) = delete; // no copying Trait(void) = delete; // no null constructor - explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, slim_effect_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); + explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_effect_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); ~Trait(void); - inline __attribute__((always_inline)) slim_trait_index_t Index(void) const { return index_; } + inline __attribute__((always_inline)) slim_trait_index_t Index(void) const { return index_; } inline __attribute__((always_inline)) void SetIndex(slim_trait_index_t p_index) { index_ = p_index; } // only from AddTrait() - inline __attribute__((always_inline)) TraitType Type(void) const { return type_; } - inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } + inline __attribute__((always_inline)) TraitType Type(void) const { return type_; } + inline __attribute__((always_inline)) bool HasLogisticPostTransform(void) const { return logistic_post_; } + inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } slim_effect_t BaselineOffset(void) const { return baselineOffset_; }; From 3ba55c6da6556a3f3c86cb70994db70c7ab80c87 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 24 Jan 2026 19:36:02 -0600 Subject: [PATCH 096/107] add slim_trait_offset_t and slim_phenotype_t, shift up to double --- core/community_eidos.cpp | 2 +- core/individual.cpp | 170 +++++++++++++++++++-------------------- core/individual.h | 10 +-- core/mutation.cpp | 68 ++++++++-------- core/slim_globals.h | 6 +- core/species_eidos.cpp | 12 +-- core/subpopulation.cpp | 28 +++---- core/trait.cpp | 18 ++--- core/trait.h | 12 +-- 9 files changed, 164 insertions(+), 162 deletions(-) diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index 62b36b0f..3e4eb728 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -534,7 +534,7 @@ void Community::SetProperty(EidosGlobalStringID p_property_id, const EidosValue old_last_valid_history_index = history_record.history_length_ - 1; for (int entry_index = new_last_valid_history_index + 1; entry_index <= old_last_valid_history_index; ++entry_index) - history[entry_index] = NAN; + history[entry_index] = std::numeric_limits::quiet_NaN(); } } diff --git a/core/individual.cpp b/core/individual.cpp index c5a2f431..657e01c0 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -145,7 +145,7 @@ void Individual::_InitializePerTraitInformation(void) #endif trait_info_ = &trait_info_0_; - trait_info_0_.phenotype_ = std::numeric_limits::quiet_NaN(); // "uncalculated" + trait_info_0_.phenotype_ = std::numeric_limits::quiet_NaN(); // "uncalculated" trait_info_0_.offset_ = traits[0]->DrawIndividualOffset(); } else if (trait_count == 0) @@ -178,7 +178,7 @@ void Individual::_InitializePerTraitInformation(void) for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { - trait_info_[trait_index].phenotype_ = std::numeric_limits::quiet_NaN(); // "uncalculated" + trait_info_[trait_index].phenotype_ = std::numeric_limits::quiet_NaN(); // "uncalculated" trait_info_[trait_index].offset_ = traits[trait_index]->DrawIndividualOffset(); } } @@ -2809,7 +2809,7 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue fitness_scaling_ = (slim_fitness_t)p_value.FloatAtIndex_NOCAST(0, nullptr); Individual::s_any_individual_fitness_scaling_set_ = true; - if ((fitness_scaling_ < 0.0f) || (std::isnan(fitness_scaling_))) + if ((fitness_scaling_ < (slim_fitness_t)0.0) || (std::isnan(fitness_scaling_))) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property fitnessScaling must be >= 0.0." << EidosTerminate(); return; @@ -2850,7 +2850,7 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue if (trait) // ACCELERATED { - slim_effect_t new_phenotype = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + slim_phenotype_t new_phenotype = (slim_phenotype_t)p_value.FloatAtIndex_NOCAST(0, nullptr); if (std::isinf(new_phenotype)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); @@ -2865,7 +2865,7 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue if (trait) { - slim_effect_t new_offset = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + slim_trait_offset_t new_offset = (slim_trait_offset_t)p_value.FloatAtIndex_NOCAST(0, nullptr); if (!std::isfinite(new_offset)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite." << EidosTerminate(); @@ -3298,7 +3298,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope if (p_source_size == 1) { - slim_effect_t new_phenotype = (slim_effect_t)source_data[0]; + slim_phenotype_t new_phenotype = (slim_phenotype_t)source_data[0]; if (std::isinf(new_phenotype)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); @@ -3315,7 +3315,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope for (size_t value_index = 0; value_index < p_values_size; ++value_index) { const Individual *value = individuals_buffer[value_index]; - slim_effect_t new_phenotype = (slim_effect_t)source_data[value_index]; + slim_phenotype_t new_phenotype = (slim_phenotype_t)source_data[value_index]; if (std::isinf(new_phenotype)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); @@ -3329,7 +3329,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope // with a mixed-species target, the species and trait have to be looked up for each individual if (p_source_size == 1) { - slim_effect_t new_phenotype = (slim_effect_t)source_data[0]; + slim_phenotype_t new_phenotype = (slim_phenotype_t)source_data[0]; if (std::isinf(new_phenotype)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); @@ -3360,7 +3360,7 @@ void Individual::SetProperty_Accelerated_TRAIT_VALUE(EidosGlobalStringID p_prope EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is not defined for object element type Individual in species " << value_species.name_ << "; trait " << property_string << " does not exist for this species." << EidosTerminate(); slim_trait_index_t trait_index = trait->Index(); - slim_effect_t new_phenotype = (slim_effect_t)source_data[value_index]; + slim_phenotype_t new_phenotype = (slim_phenotype_t)source_data[value_index]; if (std::isinf(new_phenotype)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite or NAN." << EidosTerminate(); @@ -3391,7 +3391,7 @@ void Individual::SetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_prop if (p_source_size == 1) { - slim_effect_t new_offset = (slim_effect_t)source_data[0]; + slim_trait_offset_t new_offset = (slim_trait_offset_t)source_data[0]; if (!std::isfinite(new_offset)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); @@ -3408,7 +3408,7 @@ void Individual::SetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_prop for (size_t value_index = 0; value_index < p_values_size; ++value_index) { const Individual *value = individuals_buffer[value_index]; - slim_effect_t new_offset = (slim_effect_t)source_data[value_index]; + slim_trait_offset_t new_offset = (slim_trait_offset_t)source_data[value_index]; if (!std::isfinite(new_offset)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); @@ -3422,7 +3422,7 @@ void Individual::SetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_prop // with a mixed-species target, the species and trait have to be looked up for each individual if (p_source_size == 1) { - slim_effect_t new_offset = (slim_effect_t)source_data[0]; + slim_trait_offset_t new_offset = (slim_trait_offset_t)source_data[0]; if (!std::isfinite(new_offset)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); @@ -3453,7 +3453,7 @@ void Individual::SetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_prop EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is not defined for object element type Individual in species " << value_species.name_ << "; trait " << trait_name << " does not exist for this species." << EidosTerminate(); slim_trait_index_t trait_index = trait->Index(); - slim_effect_t new_offset = (slim_effect_t)source_data[value_index]; + slim_trait_offset_t new_offset = (slim_trait_offset_t)source_data[value_index]; if (!std::isfinite(new_offset)) EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); @@ -3669,7 +3669,7 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met if (trait_indices.size() == 1) { slim_trait_index_t trait_index = trait_indices[0]; - slim_effect_t offset = trait_info_[trait_index].offset_; + slim_trait_offset_t offset = trait_info_[trait_index].offset_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)offset)); } @@ -3679,7 +3679,7 @@ EidosValue_SP Individual::ExecuteMethod_offsetForTrait(EidosGlobalStringID p_met for (slim_trait_index_t trait_index : trait_indices) { - slim_effect_t offset = trait_info_[trait_index].offset_; + slim_trait_offset_t offset = trait_info_[trait_index].offset_; float_result->push_float_no_check((double)offset); } @@ -3704,7 +3704,7 @@ EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_ if (trait_indices.size() == 1) { slim_trait_index_t trait_index = trait_indices[0]; - slim_effect_t phenotype = trait_info_[trait_index].phenotype_; + slim_phenotype_t phenotype = trait_info_[trait_index].phenotype_; return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)phenotype)); } @@ -3714,7 +3714,7 @@ EidosValue_SP Individual::ExecuteMethod_phenotypeForTrait(EidosGlobalStringID p_ for (slim_trait_index_t trait_index : trait_indices) { - slim_effect_t phenotype = trait_info_[trait_index].phenotype_; + slim_phenotype_t phenotype = trait_info_[trait_index].phenotype_; float_result->push_float_no_check((double)phenotype); } @@ -4669,11 +4669,11 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { Individual *ind = individuals_buffer[individual_index]; - slim_effect_t offset = trait->DrawIndividualOffset(); + slim_trait_offset_t offset = trait->DrawIndividualOffset(); // effects for multiplicative traits are clamped to a minimum of 0.0 - if (offset < (slim_effect_t)0.0) - offset = 0.0; + if (offset < (slim_trait_offset_t)0.0) + offset = (slim_trait_offset_t)0.0; ind->trait_info_[trait_index].offset_ = offset; } @@ -4692,7 +4692,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin else if (offset_count == 1) { // pattern 2: setting a single offset value across one or more traits in one or more individuals - slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(0, nullptr)); + slim_trait_offset_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(0, nullptr)); if (!std::isfinite(offset)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); @@ -4700,11 +4700,11 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; - slim_effect_t offset_for_trait = offset; + slim_trait_offset_t offset_for_trait = offset; // effects for multiplicative traits are clamped to a minimum of 0.0 - if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) - offset = 0.0; + if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_trait_offset_t)0.0)) + offset = (slim_trait_offset_t)0.0; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset_for_trait; @@ -4718,14 +4718,14 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; - slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(offset_index++, nullptr)); + slim_trait_offset_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(offset_index++, nullptr)); if (!std::isfinite(offset)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); // effects for multiplicative traits are clamped to a minimum of 0.0 - if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) - offset = 0.0; + if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_trait_offset_t)0.0)) + offset = (slim_trait_offset_t)0.0; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { @@ -4754,11 +4754,11 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { - slim_effect_t offset = static_cast(*(offsets_int++)); + slim_trait_offset_t offset = static_cast(*(offsets_int++)); // effects for multiplicative traits are clamped to a minimum of 0.0 - if (offset < (slim_effect_t)0.0) - offset = 0.0; + if (offset < (slim_trait_offset_t)0.0) + offset = (slim_trait_offset_t)0.0; individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; } @@ -4767,7 +4767,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { - slim_effect_t offset = static_cast(*(offsets_int++)); + slim_trait_offset_t offset = static_cast(*(offsets_int++)); individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; } @@ -4782,11 +4782,11 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; - slim_effect_t offset = static_cast(*(offsets_int++)); + slim_trait_offset_t offset = static_cast(*(offsets_int++)); // effects for multiplicative traits are clamped to a minimum of 0.0 - if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) - offset = 0.0; + if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_trait_offset_t)0.0)) + offset = (slim_trait_offset_t)0.0; ind->trait_info_[trait_index].offset_ = offset; } @@ -4808,14 +4808,14 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { - slim_effect_t offset = static_cast(*(offsets_float++)); + slim_trait_offset_t offset = static_cast(*(offsets_float++)); if (!std::isfinite(offset)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); // effects for multiplicative traits are clamped to a minimum of 0.0 - if (offset < (slim_effect_t)0.0) - offset = 0.0; + if (offset < (slim_trait_offset_t)0.0) + offset = (slim_trait_offset_t)0.0; individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; } @@ -4824,7 +4824,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin { for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { - slim_effect_t offset = static_cast(*(offsets_float++)); + slim_trait_offset_t offset = static_cast(*(offsets_float++)); if (!std::isfinite(offset)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); @@ -4842,14 +4842,14 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin for (slim_trait_index_t trait_index : trait_indices) { Trait *trait = species->Traits()[trait_index]; - slim_effect_t offset = static_cast(*(offsets_float++)); + slim_trait_offset_t offset = static_cast(*(offsets_float++)); if (!std::isfinite(offset)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires offset values to be finite (not NAN or INF)." << EidosTerminate(); // effects for multiplicative traits are clamped to a minimum of 0.0 - if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) - offset = 0.0; + if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_trait_offset_t)0.0)) + offset = (slim_trait_offset_t)0.0; ind->trait_info_[trait_index].offset_ = offset; } @@ -4893,7 +4893,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt if (phenotype_count == 1) { // pattern 1: setting a single phenotype value across one or more traits in one or more individuals - slim_effect_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(0, nullptr)); + slim_phenotype_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(0, nullptr)); if (std::isinf(phenotype)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires phenotypes to be finite or NAN." << EidosTerminate(); @@ -4924,7 +4924,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt for (slim_trait_index_t trait_index : trait_indices) { - slim_effect_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(phenotype_index++, nullptr)); + slim_phenotype_t phenotype = static_cast(phenotype_value->NumericAtIndex_NOCAST(phenotype_index++, nullptr)); if (std::isinf(phenotype)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires phenotypes to be finite or NAN." << EidosTerminate(); @@ -4952,7 +4952,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt slim_trait_index_t trait_index = trait_indices[0]; for (int individual_index = 0; individual_index < individuals_count; ++individual_index) - individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); + individuals_buffer[individual_index]->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); } else { @@ -4961,7 +4961,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt Individual *ind = individuals_buffer[individual_index]; for (slim_trait_index_t trait_index : trait_indices) - ind->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); + ind->trait_info_[trait_index].phenotype_ = static_cast(*(phenotypes_int++)); } } } @@ -4977,7 +4977,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { - slim_effect_t phenotype = static_cast(*(phenotypes_float++)); + slim_phenotype_t phenotype = static_cast(*(phenotypes_float++)); if (std::isinf(phenotype)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires phenotypes to be finite or NAN." << EidosTerminate(); @@ -4993,7 +4993,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setPhenotypeForTrait(EidosGlobalSt for (slim_trait_index_t trait_index : trait_indices) { - slim_effect_t phenotype = static_cast(*(phenotypes_float++)); + slim_phenotype_t phenotype = static_cast(*(phenotypes_float++)); if (std::isinf(phenotype)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setPhenotypeForTrait): setPhenotypeForTrait() requires phenotypes to be finite or NAN." << EidosTerminate(); @@ -6691,7 +6691,7 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv if (trait->is_pure_neutral_now_) { TraitType traitType = trait->Type(); - slim_effect_t trait_baseline_offset = trait->BaselineOffset(); + slim_trait_offset_t trait_baseline_offset = trait->BaselineOffset(); if (traitType == TraitType::kAdditive) { @@ -6707,7 +6707,7 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv { double additive_result = (double)(trait_baseline_offset + trait_info.offset_); - trait_info.phenotype_ = static_cast(1.0 / (1.0 + std::exp(- static_cast(additive_result)))); + trait_info.phenotype_ = static_cast(1.0 / (1.0 + std::exp(- static_cast(additive_result)))); } } } @@ -6720,7 +6720,7 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; if (f_force_recalc || std::isnan(trait_info.phenotype_)) - trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset + trait_info.offset_); } } } @@ -6732,7 +6732,7 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv IndividualTraitInfo &trait_info = ind->trait_info_[trait_index]; if (f_force_recalc || std::isnan(trait_info.phenotype_)) - trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset * trait_info.offset_); } } @@ -6960,7 +6960,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; TraitType traitType = trait->Type(); - slim_effect_t trait_baseline_offset = trait->BaselineOffset(); + slim_trait_offset_t trait_baseline_offset = trait->BaselineOffset(); #if DEBUG_TRAIT_DEMAND() std::cout << " DemandPhenotype_INDIVIDUALS() trait " << trait->Name() << " (" << traitType << ") has baseline offset " << trait_baseline_offset << std::endl; @@ -6979,12 +6979,12 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual if (f_force_recalc) { - trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset + trait_info.offset_); } else if (!f_force_recalc && std::isnan(trait_info.phenotype_)) { recalc_decisions[individual_index * trait_indices_count + trait_indices_index] = true; - trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset + trait_info.offset_); } // else (!f_force_recalc && !std::isnan(trait_info.phenotype_)), so we are not recalculating } @@ -7002,12 +7002,12 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual if (f_force_recalc) { - trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset * trait_info.offset_); } else if (!f_force_recalc && std::isnan(trait_info.phenotype_)) { recalc_decisions[individual_index * trait_indices_count + trait_indices_index] = true; - trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset * trait_info.offset_); } // else (!f_force_recalc && !std::isnan(trait_info.phenotype_)), so we are not recalculating } @@ -7231,11 +7231,11 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) continue; - slim_effect_t &phenotype_ref = ind->trait_info_[trait_index].phenotype_; + slim_phenotype_t &phenotype_ref = ind->trait_info_[trait_index].phenotype_; double additive_result = (double)phenotype_ref; if (std::isfinite(additive_result)) - phenotype_ref = static_cast(1.0 / (1.0 + std::exp(-additive_result))); + phenotype_ref = static_cast(1.0 / (1.0 + std::exp(-additive_result))); } } } @@ -7271,14 +7271,14 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) continue; - slim_effect_t calculated_phenotype = ind->trait_info_[trait_index].phenotype_; - slim_effect_t check_phenotype = ind->_CheckPhenotypeForTrait(trait_index); + slim_phenotype_t calculated_phenotype = ind->trait_info_[trait_index].phenotype_; + slim_phenotype_t check_phenotype = ind->_CheckPhenotypeForTrait(trait_index); // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait // calculations, so I use a larger threshold here of 1e-5; we'll see how this does in practice. // The goal is not to check for exact equality, but to find bugs that make calculations incorrect. - if (std::abs(calculated_phenotype - check_phenotype) > (slim_effect_t)1e-5) + if (std::abs(calculated_phenotype - check_phenotype) > (slim_phenotype_t)1e-5) EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) phenotype check failed for trait " << species->Traits()[trait_index]->Name() << " (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); } } @@ -7340,7 +7340,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; TraitType traitType = trait->Type(); - slim_effect_t trait_baseline_offset = trait->BaselineOffset(); + slim_trait_offset_t trait_baseline_offset = trait->BaselineOffset(); #if DEBUG_TRAIT_DEMAND() std::cout << " DemandPhenotype_SUBPOP() trait " << trait->Name() << " (" << traitType << ") has baseline offset " << trait_baseline_offset << std::endl; @@ -7359,12 +7359,12 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s if (f_force_recalc) { - trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset + trait_info.offset_); } else if (!f_force_recalc && std::isnan(trait_info.phenotype_)) { recalc_decisions[individual_index * trait_indices_count + trait_indices_index] = true; - trait_info.phenotype_ = trait_baseline_offset + trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset + trait_info.offset_); } // else (!f_force_recalc && !std::isnan(trait_info.phenotype_)), so we are not recalculating } @@ -7382,12 +7382,12 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s if (f_force_recalc) { - trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset * trait_info.offset_); } else if (!f_force_recalc && std::isnan(trait_info.phenotype_)) { recalc_decisions[individual_index * trait_indices_count + trait_indices_index] = true; - trait_info.phenotype_ = trait_baseline_offset * trait_info.offset_; + trait_info.phenotype_ = (slim_phenotype_t)(trait_baseline_offset * trait_info.offset_); } // else (!f_force_recalc && !std::isnan(trait_info.phenotype_)), so we are not recalculating } @@ -7654,11 +7654,11 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) continue; - slim_effect_t &phenotype_ref = ind->trait_info_[trait_index].phenotype_; + slim_phenotype_t &phenotype_ref = ind->trait_info_[trait_index].phenotype_; double additive_result = (double)phenotype_ref; if (std::isfinite(additive_result)) - phenotype_ref = static_cast(1.0 / (1.0 + std::exp(-additive_result))); + phenotype_ref = static_cast(1.0 / (1.0 + std::exp(-additive_result))); } } } @@ -7677,14 +7677,14 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) continue; - slim_effect_t calculated_phenotype = ind->trait_info_[trait_index].phenotype_; - slim_effect_t check_phenotype = ind->_CheckPhenotypeForTrait(trait_index); + slim_phenotype_t calculated_phenotype = ind->trait_info_[trait_index].phenotype_; + slim_phenotype_t check_phenotype = ind->_CheckPhenotypeForTrait(trait_index); // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait // calculations, so I use a larger threshold here of 1e-5; we'll see how this does in practice. // The goal is not to check for exact equality, but to find bugs that make calculations incorrect. - if (std::abs(calculated_phenotype - check_phenotype) > (slim_effect_t)1e-5) + if (std::abs(calculated_phenotype - check_phenotype) > (slim_phenotype_t)1e-5) EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_SUBPOP): (internal error) phenotype check failed for trait " << species->Traits()[trait_index]->Name() << " (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); } } @@ -7793,7 +7793,7 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos } } - trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_phenotype_t)effect_accumulator; } template void Individual::_IncorporateEffects_Haploid(Species *, Haplosome *, Trait *, std::vector &); @@ -8171,7 +8171,7 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos } } - trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_phenotype_t)effect_accumulator; } template void Individual::_IncorporateEffects_Diploid(Species *, Haplosome *, Haplosome *, Trait *, std::vector &); @@ -8209,7 +8209,7 @@ void Individual::_IncorporateEffects_IndependentDominance(Haplosome *haplosome, effect_accumulator *= (double)mutrun->independent_dominance_cache_for_trait(trait_index); } - trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_phenotype_t)effect_accumulator; } template void Individual::_IncorporateEffects_IndependentDominance(Haplosome *, slim_trait_index_t); @@ -8221,7 +8221,7 @@ template void Individual::_IncorporateEffects_IndependentDominance(Haploso // the rest of the code below is for checking the correctness of the calculations performed by the code above -slim_effect_t Individual::_CheckPhenotypeForTrait(slim_trait_index_t trait_index) +slim_phenotype_t Individual::_CheckPhenotypeForTrait(slim_trait_index_t trait_index) { Subpopulation *subpop = subpopulation_; Species &species = subpop->species_; @@ -8229,18 +8229,18 @@ slim_effect_t Individual::_CheckPhenotypeForTrait(slim_trait_index_t trait_index Trait *trait = species.Traits()[trait_index]; TraitType traitType = trait->Type(); int haplosome_index = 0; - slim_effect_t trait_value = trait->BaselineOffset(); + slim_trait_offset_t trait_offsets = trait->BaselineOffset(); IndividualTraitInfo &trait_info = trait_info_[trait_index]; if (traitType == TraitType::kAdditive) - trait_value += trait_info.offset_; + trait_offsets += trait_info.offset_; else // (traitType == TraitType::kMultiplicative) - trait_value *= trait_info.offset_; + trait_offsets *= trait_info.offset_; // because the low-level functions are designed to combine their effects into our phenotype, // we save the phenotype value here and restore it at the end so we leave it untouched - slim_effect_t saved_phenotype = trait_info.phenotype_; - trait_info.phenotype_ = trait_value; + slim_phenotype_t saved_phenotype = trait_info.phenotype_; + trait_info.phenotype_ = (slim_phenotype_t)trait_offsets; // determine the versions of _IncorporateEffects_X() we will use; note that we use special // variants that do not use the non-neutral caches, since that is what we want to check! @@ -8308,11 +8308,11 @@ slim_effect_t Individual::_CheckPhenotypeForTrait(slim_trait_index_t trait_index } // get the calculated trait value back out - trait_value = trait_info.phenotype_; + slim_phenotype_t trait_value = trait_info.phenotype_; // post-process logistic trait values if (trait->HasLogisticPostTransform()) - trait_value = static_cast(1.0 / (1.0 + std::exp(- static_cast(trait_value)))); + trait_value = static_cast(1.0 / (1.0 + std::exp(- static_cast(trait_value)))); // finally, restore our saved phenotype so we don't modify the official individual state trait_info.phenotype_ = saved_phenotype; @@ -8411,7 +8411,7 @@ void Individual::_Check_IncorporateEffects_Haploid(Species *species, Haplosome * effect_accumulator *= mutrun_effect_accumulator; } - trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_phenotype_t)effect_accumulator; } void Individual::_Check_IncorporateEffects_Hemizygous(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks) @@ -8485,7 +8485,7 @@ void Individual::_Check_IncorporateEffects_Hemizygous(Species *species, Haplosom } } - trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_phenotype_t)effect_accumulator; } void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome *haplosome1, Haplosome *haplosome2, Trait *trait, std::vector &p_mutationEffect_callbacks) @@ -8850,7 +8850,7 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * effect_accumulator *= mutrun_effect_accumulator; } - trait_info_[trait_index].phenotype_ = (slim_effect_t)effect_accumulator; + trait_info_[trait_index].phenotype_ = (slim_phenotype_t)effect_accumulator; } diff --git a/core/individual.h b/core/individual.h index 01a02593..0dc36d48 100644 --- a/core/individual.h +++ b/core/individual.h @@ -67,10 +67,11 @@ inline slim_pedigreeid_t SLiM_GetNextPedigreeID_Block(int p_block_size) // This struct contains all information for a single trait in a single individual. In a multitrait // model, each individual has a pointer to a buffer of these records, providing per-trait information. +// BCH 1/24/2026: This now contains two doubles, for better precision; the error with float was large. typedef struct _IndividualTraitInfo { - slim_effect_t phenotype_; // the phenotypic value for a trait - slim_effect_t offset_; // the individual offset combined in to produce a trait value + slim_phenotype_t phenotype_; // the phenotypic value for a trait + slim_trait_offset_t offset_; // the individual offset combined in to produce a trait value } IndividualTraitInfo; class Individual : public EidosDictionaryUnretained @@ -143,8 +144,7 @@ class Individual : public EidosDictionaryUnretained slim_usertag_t tag_value_; // a user-defined tag value of integer type double tagF_value_; // a user-defined tag value of float type - // FIXME MULTITRAIT: We should also have a typedef for trait indices, and it should be int32_t, again for speed/size; get rid of int64_t for this - slim_fitness_t fitness_scaling_ = 1.0f; // the fitnessScaling property value + slim_fitness_t fitness_scaling_ = (slim_fitness_t)1.0; // the fitnessScaling property value slim_fitness_t cached_fitness_UNSAFE_; // the last calculated fitness value for this individual; NaN for new offspring, 1.0 for new subpops // this is marked UNSAFE because Subpopulation's individual_cached_fitness_OVERRIDE_ flag can override // this value in constant-fitness models; that flag must be checked before using this cached value @@ -427,7 +427,7 @@ class Individual : public EidosDictionaryUnretained #endif // Debugging checkback for phenotype calculation; this is very slow, and does not use the non-neutral cache - slim_effect_t _CheckPhenotypeForTrait(slim_trait_index_t trait_index); + slim_phenotype_t _CheckPhenotypeForTrait(slim_trait_index_t trait_index); void _Check_IncorporateEffects_Haploid(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); void _Check_IncorporateEffects_Hemizygous(Species *species, Haplosome *haplosome, Trait *trait, std::vector &p_mutationEffect_callbacks); diff --git a/core/mutation.cpp b/core/mutation.cpp index 1f9f2a28..9fd457eb 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -105,15 +105,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (traitType == TraitType::kMultiplicative) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect_size); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect_size); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect_size); + traitInfoRec->homozygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + effect_size); + traitInfoRec->heterozygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + hemizygous_dominance * effect_size); } else // (traitType == TraitType::kAdditive) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect_size); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect_size); + traitInfoRec->homozygous_effect_ = ((slim_effect_t)2.0 * effect_size); + traitInfoRec->heterozygous_effect_ = ((slim_effect_t)2.0 * realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = ((slim_effect_t)2.0 * hemizygous_dominance * effect_size); } } else // (effect == 0.0) @@ -246,15 +246,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (traitType == TraitType::kMultiplicative) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect_size); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect_size); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect_size); + traitInfoRec->homozygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + effect_size); + traitInfoRec->heterozygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + hemizygous_dominance * effect_size); } else // (traitType == TraitType::kAdditive) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect_size); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect_size); + traitInfoRec->homozygous_effect_ = ((slim_effect_t)2.0 * effect_size); + traitInfoRec->heterozygous_effect_ = ((slim_effect_t)2.0 * realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = ((slim_effect_t)2.0 * hemizygous_dominance * effect_size); } } else // (effect == 0.0) @@ -353,15 +353,15 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (traitType == TraitType::kMultiplicative) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect_size); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect_size); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect_size); + traitInfoRec->homozygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + effect_size); + traitInfoRec->heterozygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + hemizygous_dominance * effect_size); } else // (traitType == TraitType::kAdditive) { - traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect_size); - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); - traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect_size); + traitInfoRec->homozygous_effect_ = ((slim_effect_t)2.0 * effect_size); + traitInfoRec->heterozygous_effect_ = ((slim_effect_t)2.0 * realized_dominance * effect_size); + traitInfoRec->hemizygous_effect_ = ((slim_effect_t)2.0 * hemizygous_dominance * effect_size); } } else // (effect == 0.0) @@ -482,15 +482,15 @@ void Mutation::SelfConsistencyCheck(const std::string &p_message_end) const if (trait->Type() == TraitType::kAdditive) { - correct_homozygous_effect = (slim_effect_t)(2.0f * effect_size); // 2a - correct_heterozygous_effect = (slim_effect_t)(2.0f * dominance * effect_size); // 2ha - correct_hemizygous_effect = (slim_effect_t)(2.0f * hemizygous_dominance * effect_size); // 2ha (using h_hemi) + correct_homozygous_effect = ((slim_effect_t)2.0 * effect_size); // 2a + correct_heterozygous_effect = ((slim_effect_t)2.0 * dominance * effect_size); // 2ha + correct_hemizygous_effect = ((slim_effect_t)2.0 * hemizygous_dominance * effect_size); // 2ha (using h_hemi) } else { - correct_homozygous_effect = (slim_effect_t)std::max(0.0f, 1.0f + effect_size); // 1 + s - correct_heterozygous_effect = (slim_effect_t)std::max(0.0f, 1.0f + dominance * effect_size); // 1 + hs - correct_hemizygous_effect = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect_size); // 1 + hs (using h_hemi) + correct_homozygous_effect = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + effect_size); // 1 + s + correct_heterozygous_effect = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + dominance * effect_size); // 1 + hs + correct_hemizygous_effect = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + hemizygous_dominance * effect_size); // 1 + hs (using h_hemi) } if (correct_homozygous_effect != traitInfoRec.homozygous_effect_) @@ -592,16 +592,16 @@ void Mutation::SetEffectSize(Trait *p_trait, MutationTraitInfo *traitInfoRec, sl // is admittedly a philosophical issue here; if a multiplicative trait represented simply some abstract // trait with no direct connection to fitness, then maybe clamping here would not make sense? But even // then, negative effects don't really seem to me to make sense there, so I think this is good. - traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_effect); // 1 + s - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * p_new_effect); // 1 + hs - traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * p_new_effect); // 1 + hs (using h_hemi) + traitInfoRec->homozygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + p_new_effect); // 1 + s + traitInfoRec->heterozygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + realized_dominance * p_new_effect); // 1 + hs + traitInfoRec->hemizygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + hemizygous_dominance * p_new_effect); // 1 + hs (using h_hemi) } else // (p_trait->Type() == TraitType::kAdditive) { // For additive traits, the baseline of the trait is arbitrary and there is no cutoff. - traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * p_new_effect); // 2a - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * p_new_effect); // 2ha - traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * p_new_effect); // 2ha (using h_hemi) + traitInfoRec->homozygous_effect_ = ((slim_effect_t)2.0 * p_new_effect); // 2a + traitInfoRec->heterozygous_effect_ = ((slim_effect_t)2.0 * realized_dominance * p_new_effect); // 2ha + traitInfoRec->hemizygous_effect_ = ((slim_effect_t)2.0 * hemizygous_dominance * p_new_effect); // 2ha (using h_hemi) } } else // p_new_effect == 0.0; therefore, old_effect != 0.0 @@ -644,11 +644,11 @@ void Mutation::SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, sli if (p_trait->Type() == TraitType::kMultiplicative) { - traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect_size); + traitInfoRec->heterozygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + realized_dominance * effect_size); } else // (p_trait->Type() == TraitType::kAdditive) { - traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); + traitInfoRec->heterozygous_effect_ = ((slim_effect_t)2.0 * realized_dominance * effect_size); } EvaluateFlags(); @@ -667,11 +667,11 @@ void Mutation::SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitIn if (p_trait->Type() == TraitType::kMultiplicative) { - traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_dominance * traitInfoRec->effect_size_); + traitInfoRec->hemizygous_effect_ = std::max((slim_effect_t)0.0, (slim_effect_t)1.0 + p_new_dominance * traitInfoRec->effect_size_); } else // (p_trait->Type() == TraitType::kAdditive) { - traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); + traitInfoRec->hemizygous_effect_ = ((slim_effect_t)2.0 * p_new_dominance * traitInfoRec->effect_size_); } // No need for this here; hemizygous dominance does not affect our flags diff --git a/core/slim_globals.h b/core/slim_globals.h index f19a1ad8..48355f8c 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -126,8 +126,10 @@ typedef int64_t slim_haplosomeid_t; // identifiers for pedigreed haplosomes; no typedef int32_t slim_polymorphismid_t; // identifiers for polymorphisms, which need only 32 bits since they are only segregating mutations typedef int32_t slim_trait_index_t; // indices for traits; we are limited to 256 traits by SLIM_MAX_TRAITS at present, so this is plenty of room typedef uint32_t slim_operation_id_t; // used for MutationRun's operation_id_, as a unique identifier of a given task being worked upon -typedef float slim_effect_t; // storage of mutation effect sizes, trait effects, and dominance coefficients in memory-tight classes -typedef float slim_fitness_t; // storage of fitness effects (e.g., fitnessScaling values) and final individual fitness values +typedef float slim_effect_t; // storage of mutation effect sizes (and dominance coefficients), float for memory-tight classes +typedef double slim_trait_offset_t; // storage of baseline offsets and individual offsets +typedef double slim_phenotype_t; // storage of trait values (phenotypes), trait effects, and individual phenotypes for traits +typedef double slim_fitness_t; // storage of fitness effects (e.g., fitnessScaling values) and final individual fitness values #define SLIM_MAX_TICK (1000000000L) // ticks range from 0 (init time) to this; SLIM_MAX_TICK + 1 is an "infinite" marker value #define SLIM_MAX_BASE_POSITION (1000000000000000L) // base positions in the chromosome can range from 0 to 1e15; see above diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 44ec687b..08870a1d 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1701,11 +1701,11 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires type to be either 'multiplicative' (or 'm'), 'additive' (or 'a'), or 'logistic' (or 'l')." << EidosTerminate(); // baselineOffset - slim_effect_t baselineOffset; + slim_trait_offset_t baselineOffset; if (baselineOffset_value->Type() == EidosValueType::kValueNULL) { - baselineOffset = (slim_effect_t)((type == TraitType::kMultiplicative) ? 1.0 : 0.0); + baselineOffset = (slim_trait_offset_t)((type == TraitType::kMultiplicative) ? 1.0 : 0.0); } else { @@ -1714,14 +1714,14 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string if (!std::isfinite(baselineOffset_double)) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires baselineOffset to be a finite value (not NAN or INF)." << EidosTerminate(); - baselineOffset = (slim_effect_t)baselineOffset_double; // this can round to infinity + baselineOffset = (slim_trait_offset_t)baselineOffset_double; // this can round to infinity if (!std::isfinite(baselineOffset)) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires baselineOffset to be representable as a finite single-precision floating-point number; the value given rounded to infinity." << EidosTerminate(); } - if ((type == TraitType::kMultiplicative) && (baselineOffset < (slim_effect_t)0.0)) - baselineOffset = (slim_effect_t)0.0; + if ((type == TraitType::kMultiplicative) && (baselineOffset < (slim_trait_offset_t)0.0)) + baselineOffset = (slim_trait_offset_t)0.0; // individualOffsetMean double individualOffsetMean = individualOffsetMean_value->FloatAtIndex_NOCAST(0, nullptr); @@ -1961,7 +1961,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string std::ostream &output_stream = p_interpreter.ExecutionOutputStream(); output_stream << "initializeTrait(name='" << name << "', type='" << type_string << "'"; - if (baselineOffset != (slim_effect_t)0.0) + if (baselineOffset != (slim_trait_offset_t)0.0) output_stream << ", baselineOffset=" << baselineOffset << ""; if (individualOffsetMean != 0.0) output_stream << ", individualOffsetMean=" << individualOffsetMean << ""; diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 3b0c6090..778ca045 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1368,7 +1368,7 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio #ifdef SLIMGUI for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) - parent_individuals_[individual_index]->cached_unscaled_fitness_ = (slim_effect_t)constant_unscaled_fitness_value; + parent_individuals_[individual_index]->cached_unscaled_fitness_ = (slim_fitness_t)constant_unscaled_fitness_value; #endif } else // (model_type_ == SLiMModelType::kModelTypeNonWF) @@ -1381,10 +1381,10 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio Individual *ind = parent_individuals_[individual_index]; #ifdef SLIMGUI - ind->cached_unscaled_fitness_ = (slim_effect_t)constant_unscaled_fitness_value; + ind->cached_unscaled_fitness_ = (slim_fitness_t)constant_unscaled_fitness_value; #endif - ind->cached_fitness_UNSAFE_ = (slim_effect_t)constant_fitness_value; + ind->cached_fitness_UNSAFE_ = (slim_fitness_t)constant_fitness_value; } } } @@ -1418,7 +1418,7 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio // then loop over individuals and pull together the relevant phenotype values, fitnessEffect() callbacks, // subpopulation fitnessScaling, and individual fitnessScaling to produce final individual fitness values; // we choose our _CalculateFitnessAfterDemand() template based upon flags and execute it to calculate fitness values - bool f_has_subpop_fitnessScaling = (subpop_fitness_scaling != 1.0f); + bool f_has_subpop_fitnessScaling = (subpop_fitness_scaling != (slim_fitness_t)1.0); bool f_has_ind_fitnessScaling = Individual::s_any_individual_fitness_scaling_set_; bool f_has_fitnessEffect_callbacks = (p_subpop_fitnessEffect_callbacks.size() > 0); bool f_has_trait_direct_effects = (p_direct_effect_trait_indices.size() > 0); @@ -1514,12 +1514,12 @@ void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p if (f_single_trait) { - fitness *= trait_info[single_trait_index].phenotype_; + fitness *= (slim_fitness_t)trait_info[single_trait_index].phenotype_; } else { for (slim_trait_index_t trait_index : p_direct_effect_trait_indices) - fitness *= trait_info[trait_index].phenotype_; // >= 0.0 for multiplicative traits + fitness *= (slim_fitness_t)trait_info[trait_index].phenotype_; // >= 0.0 for multiplicative traits } } @@ -2296,7 +2296,7 @@ Individual *Subpopulation::GenerateIndividualCrossed(Individual *p_parent1, Indi } // Create the offspring and record it - Individual *individual = NewSubpopIndividual(/* index */ -1, p_child_sex, /* age */ 0, /* fitness */ NAN, /* p_mean_parent_age */ (p_parent1->age_ + (float)p_parent2->age_) / 2.0F); + Individual *individual = NewSubpopIndividual(/* index */ -1, p_child_sex, /* age */ 0, /* fitness */ std::numeric_limits::quiet_NaN(), /* p_mean_parent_age */ (p_parent1->age_ + (float)p_parent2->age_) / 2.0F); slim_pedigreeid_t individual_pid = f_pedigree_rec ? SLiM_GetNextPedigreeID() : 0; @@ -2680,7 +2680,7 @@ Individual *Subpopulation::GenerateIndividualSelfed(Individual *p_parent) } // Create the offspring and record it - Individual *individual = NewSubpopIndividual(/* index */ -1, IndividualSex::kHermaphrodite, /* age */ 0, /* fitness */ NAN, /* p_mean_parent_age */ p_parent->age_); + Individual *individual = NewSubpopIndividual(/* index */ -1, IndividualSex::kHermaphrodite, /* age */ 0, /* fitness */ std::numeric_limits::quiet_NaN(), /* p_mean_parent_age */ p_parent->age_); slim_pedigreeid_t individual_pid = f_pedigree_rec ? SLiM_GetNextPedigreeID() : 0; @@ -2881,7 +2881,7 @@ Individual *Subpopulation::GenerateIndividualCloned(Individual *p_parent) } // Create the offspring and record it - Individual *individual = NewSubpopIndividual(/* index */ -1, parent_sex, /* age */ 0, /* fitness */ NAN, /* p_mean_parent_age */ p_parent->age_); + Individual *individual = NewSubpopIndividual(/* index */ -1, parent_sex, /* age */ 0, /* fitness */ std::numeric_limits::quiet_NaN(), /* p_mean_parent_age */ p_parent->age_); slim_pedigreeid_t individual_pid = f_pedigree_rec ? SLiM_GetNextPedigreeID() : 0; @@ -5522,7 +5522,7 @@ void Subpopulation::SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p { slim_fitness_t source_value = (slim_fitness_t)p_source.FloatAtIndex_NOCAST(0, nullptr); - if ((source_value < 0.0f) || std::isnan(source_value)) + if ((source_value < (slim_fitness_t)0.0) || std::isnan(source_value)) EIDOS_TERMINATION << "ERROR (Subpopulation::SetProperty_Accelerated_fitnessScaling): property fitnessScaling must be >= 0.0." << EidosTerminate(); for (size_t value_index = 0; value_index < p_values_size; ++value_index) @@ -5536,7 +5536,7 @@ void Subpopulation::SetProperty_Accelerated_fitnessScaling(EidosGlobalStringID p { slim_fitness_t source_value = (slim_fitness_t)source_data[value_index]; - if ((source_value < 0.0f) || std::isnan(source_value)) + if ((source_value < (slim_fitness_t)0.0) || std::isnan(source_value)) EIDOS_TERMINATION << "ERROR (Subpopulation::SetProperty_Accelerated_fitnessScaling): property fitnessScaling must be >= 0.0." << EidosTerminate(); ((Subpopulation *)(p_values[value_index]))->subpop_fitness_scaling_ = source_value; @@ -6090,7 +6090,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_addEmpty(EidosGlobalStringID p_method Individual *individual = GenerateIndividualEmpty(/* index */ -1, /* sex */ child_sex, /* age */ 0, - /* fitness */ NAN, + /* fitness */ std::numeric_limits::quiet_NaN(), /* mean_parent_age */ 0.0F, /* haplosome1_null */ haplosome1_null, /* haplosome2_null */ haplosome2_null, @@ -6545,7 +6545,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_addMultiRecombinant(EidosGlobalString child_sex = (Eidos_RandomBool(rng_state) ? IndividualSex::kMale : IndividualSex::kFemale); // Make the new individual as a candidate; we make its haplosomes in the pass 2 loop below - Individual *individual = NewSubpopIndividual(/* index */ -1, child_sex, /* age */ 0, /* fitness */ NAN, mean_parent_age); + Individual *individual = NewSubpopIndividual(/* index */ -1, child_sex, /* age */ 0, /* fitness */ std::numeric_limits::quiet_NaN(), mean_parent_age); slim_pedigreeid_t pid = 0; if (pedigrees_enabled) @@ -7355,7 +7355,7 @@ EidosValue_SP Subpopulation::ExecuteMethod_addRecombinant(EidosGlobalStringID p_ // but HaplosomeRecombined() now handles that for us since it is shared functionality. // Make the new individual as a candidate - Individual *individual = NewSubpopIndividual(/* index */ -1, child_sex, /* age */ 0, /* fitness */ NAN, mean_parent_age); + Individual *individual = NewSubpopIndividual(/* index */ -1, child_sex, /* age */ 0, /* fitness */ std::numeric_limits::quiet_NaN(), mean_parent_age); Haplosome *haplosome1 = haplosome1_null ? chromosome->NewHaplosome_NULL(individual, 0) : chromosome->NewHaplosome_NONNULL(individual, 0); Haplosome *haplosome2 = nullptr; diff --git a/core/trait.cpp b/core/trait.cpp index 4d8c33b7..ca31e91f 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -11,7 +11,7 @@ #include "species.h" -Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_effect_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect) : +Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect) : index_(-1), name_(p_name), type_(p_type), logistic_post_(p_logistic_post), individualOffsetMean_(p_individualOffsetMean), individualOffsetSD_(p_individualOffsetSD), directFitnessEffect_(p_directFitnessEffect), community_(p_species.community_), species_(p_species) @@ -28,7 +28,7 @@ Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, bo EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) logistic post-transformation is only supported for additive traits." << EidosTerminate(); // effects for multiplicative traits clip at 0.0 - if ((type_ == TraitType::kMultiplicative) && (p_baselineOffset < (slim_effect_t)0.0)) + if ((type_ == TraitType::kMultiplicative) && (p_baselineOffset < (slim_trait_offset_t)0.0)) baselineOffset_ = 0.0; else baselineOffset_ = p_baselineOffset; @@ -47,12 +47,12 @@ void Trait::_RecacheIndividualOffsetDistribution(void) { // multiplicative traits use an exp() transformation to get a lognormal distribution // (effects for multiplicative traits also clip at 0.0, but exp() guarantees that anyway) - individualOffsetFixedValue_ = static_cast(std::exp(individualOffsetMean_)); + individualOffsetFixedValue_ = static_cast(std::exp(individualOffsetMean_)); } else { // additive and logistic traits use a normal distribution, so the mean is the mean - individualOffsetFixedValue_ = static_cast(individualOffsetMean_); + individualOffsetFixedValue_ = static_cast(individualOffsetMean_); } } else @@ -76,7 +76,7 @@ void Trait::Print(std::ostream &p_ostream) const p_ostream << Class()->ClassNameForDisplay() << "<" << name_ << ">"; } -slim_effect_t Trait::_DrawIndividualOffset(void) const +slim_trait_offset_t Trait::_DrawIndividualOffset(void) const { // draws from a normal distribution defined by individualOffsetMean_ and individualOffsetSD_ // note the individualOffsetSD_ == 0 case was already handled by DrawIndividualOffset() @@ -88,14 +88,14 @@ slim_effect_t Trait::_DrawIndividualOffset(void) const // (effects for multiplicative traits also clip at 0.0, but exp() guarantees that anyway) double normal_draw = gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_; - return static_cast(std::exp(normal_draw)); + return static_cast(std::exp(normal_draw)); } else { // additive and logistic traits use a normal distribution, so the mean is the mean double normal_draw = gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_; - return static_cast(normal_draw); + return static_cast(normal_draw); } } @@ -190,9 +190,9 @@ void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_v // effects for multiplicative traits clip at 0.0 if ((type_ == TraitType::kMultiplicative) && (value < 0.0)) - baselineOffset_ = 0.0; + baselineOffset_ = (slim_trait_offset_t)0.0; else - baselineOffset_ = (slim_effect_t)value; + baselineOffset_ = (slim_trait_offset_t)value; return; } diff --git a/core/trait.h b/core/trait.h index b66f9530..f83babdd 100644 --- a/core/trait.h +++ b/core/trait.h @@ -63,7 +63,7 @@ class Trait : public EidosDictionaryRetained bool logistic_post_; // baseline offset, added to the trait value of every individual - slim_effect_t baselineOffset_; + slim_trait_offset_t baselineOffset_; // default individual offset distribution parameters, used to generate per-individual offsets double individualOffsetMean_; @@ -72,7 +72,7 @@ class Trait : public EidosDictionaryRetained // an optimization for the individual offset distribution, caching a fixed offset value if individualOffsetSD_ // is 0.0; note that the cached fixed value here includes the exp() transform for multiplicative traits bool individualOffsetFixed_; // true if individualOffsetSD_ == 0.0 - slim_effect_t individualOffsetFixedValue_; // pre-calculated and pre-cast for speed + slim_trait_offset_t individualOffsetFixedValue_; // pre-calculated and pre-cast for speed // if true, the calculated trait value is used directly as a fitness effect, automatically // this mimics the previous behavior of SLiM, for multiplicative traits @@ -127,7 +127,7 @@ class Trait : public EidosDictionaryRetained Trait& operator=(const Trait&) = delete; // no copying Trait(void) = delete; // no null constructor - explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_effect_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); + explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); ~Trait(void); inline __attribute__((always_inline)) slim_trait_index_t Index(void) const { return index_; } @@ -136,11 +136,11 @@ class Trait : public EidosDictionaryRetained inline __attribute__((always_inline)) bool HasLogisticPostTransform(void) const { return logistic_post_; } inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } - slim_effect_t BaselineOffset(void) const { return baselineOffset_; }; + slim_trait_offset_t BaselineOffset(void) const { return baselineOffset_; }; void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetFixed_ and individualOffsetFixedValue_ - slim_effect_t _DrawIndividualOffset(void) const; // draws from the distribution defined by individualOffsetMean_ and individualOffsetSD_ - inline __attribute__((always_inline)) slim_effect_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } + slim_trait_offset_t _DrawIndividualOffset(void) const; // draws from the distribution defined by individualOffsetMean_ and individualOffsetSD_ + inline __attribute__((always_inline)) slim_trait_offset_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } inline __attribute__((always_inline)) bool HasDirectFitnessEffect(void) const { return directFitnessEffect_; } From 7ba55bee60c23895ad4c10a9155c35a203650270 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Tue, 27 Jan 2026 20:59:42 -0600 Subject: [PATCH 097/107] add baseline accumulation feature --- QtSLiM/help/SLiMHelpClasses.html | 4 +- QtSLiM/help/SLiMHelpFunctions.html | 7 +- SLiMgui/SLiMHelpClasses.rtf | 34 ++++++++- SLiMgui/SLiMHelpFunctions.rtf | 38 ++++++++-- VERSIONS | 1 + core/community_eidos.cpp | 2 +- core/haplosome.cpp | 2 + core/individual.cpp | 5 ++ core/individual.h | 3 +- core/population.cpp | 4 ++ core/slim_globals.cpp | 1 + core/slim_globals.h | 2 + core/slim_test_genetics.cpp | 108 +++++++++++++++++++++++------ core/species.cpp | 69 +++++++++++++++++- core/species.h | 2 + core/species_eidos.cpp | 8 ++- core/trait.cpp | 25 ++++--- core/trait.h | 9 ++- 18 files changed, 270 insertions(+), 54 deletions(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index b8776e46..919d0beb 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -1440,11 +1440,13 @@

Returns whether the substitution is configured for independent dominance for the trait(s) specified by trait.  The traits can be specified as integer indices or string names of traits in the species, or directly as Trait objects; NULL represents all of the traits in the species, in the order in which they were defined.  A substitution is configured for independent dominance for a trait if the original mutation inherited a default dominance of NAN for the trait from its mutation type, or if the original mutation’s dominance for the trait was subsequently set to NAN with setDominanceForTrait(); see the class Trait documentation for discussion of this feature.  Independent dominance flags for a given target substitution will be returned consecutively in the order in which the traits are specified by trait.

5.19  Class Trait

5.19.1  Trait properties

+

baselineAccumulation => (logical$)

+

A logical flag indicating whether the trait accumulates the effects of substitutions into its baseline offset or not.  This governs what happens when a mutation that has fixed is converted into a Substitution object, as controlled by MutationType’s convertToSubstitution property.  If baselineAccumulation is T, the effect of the substituted mutation on this trait is combined (additively or multiplicatively) into the baseline offset of this trait, such that the mutation’s effect continues to be present in the simulation even though it has been substituted.  If F, this does not occur and so the effect of the substituted mutation on this trait will no longer be present (unless it is handled in script in some way).  This flag is controlled by the baselineAccumulation parameter to initializeTrait(); for the default trait, it is always F.

baselineOffset <–> (float$)

The baseline offset for the trait.  This value is combined – multiplicatively for multiplicative traits, additively for additive and logistic traits – with all other effects that influence the trait.  This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  A baseline offset value set through this property may therefore not be the value used by SLiM or subsequently returned by this property.

directFitnessEffect => (logical$)

-

A logical flag indicating whether the trait has a direct fitness effect or not.  If T, the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component.  If F, the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a “fitness function”), or the trait might have other effects that are not obviously related to fitness at all.

+

A logical flag indicating whether the trait has a direct fitness effect or not.  If T, the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component.  If F, the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a “fitness function”), or the trait might have other effects that are not obviously related to fitness at all.  This flag is controlled by the defaultFitnessEffect parameter to initializeTrait(); for the default trait, it is always T.

index => (integer$)

The index of the trait in the vector of traits kept by the species.  The first trait defined in a species is at index 0, and subsequent traits count upwards from there.  The index of a trait is often used to refer to the trait, so it is important.  A global constant is defined for every trait, using each trait’s name, that provides the index of each trait, so this property will probably rarely be needed.

individualOffsetMean <–> (float$)

diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 0fd9f26c..22cf5e53 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -144,14 +144,15 @@

The tickModulo and tickPhase parameters determine the activation schedule for the species.  The active property of the species will be set to T (thus activating the species) every tickModulo ticks, beginning in tick tickPhase.  (However, when the species is activated in a given tick, the skipTick() method may still be called in a first() event to deactivate it.)  See the active property of Species for more details.

The avatar parameter, if not "", sets a string value used to represent the species graphically, particularly in SLiMgui but perhaps in other contexts also.  The avatar should generally be a single character – usually an emoji corresponding to the species, such as "🦊" for foxes or "🐭" for mice.  If avatar is the empty string, "", SLiMgui will choose a default avatar.

The color parameter, if not "", sets a string color value used to represent the species in SLiMgui.  Colors may be specified by name, or with hexadecimal RGB values of the form "#RRGGBB" (see the Eidos manual for details).  If color is the empty string, "", SLiMgui will choose a default color.

-

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [float$ individualOffsetMean = 0.0], [float$ individualOffsetSD = 0.0], [logical$ directFitnessEffect = F])

+

(object<Trait>$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [float$ individualOffsetMean = 0.0], [float$ individualOffsetSD = 0.0], [logical$ directFitnessEffect = F], [logical$ baselineAccumulation = T])

Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized.  The new Trait object is returned.  For more details on the way that traits work in SLiM, beyond what is given below, see the Trait class documentation.

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the Individual, Species, Mutation, or Substitution classes.  These requirements are necessary because, after the new trait is created, new properties are added to those classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named height, getting and setting an individual’s trait value would be possible through the property individual.height.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.height would provide the Trait object named height.  See the Mutation and Substitution classes for details on the trait-related properties defined for them.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("height", height) would allow the Trait object to be referenced simply as height.

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model); "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model); or "logistic", if the trait value should be the result of a logistic transformation of an additive trait value (for modeling a trait that represents a probability, such as a disease risk).  (Because the logistic trait type is based upon an underlying additive trait value that is transformed, it will often be grouped together in this manual as having “additive” effects; this is in reference to that underlying model, prior to the logistic transformation.)  The shorter versions "m", "a", and "l" are also allowed.

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive and logistic traits, such that the baseline offset has no effect upon the trait value.  Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of 0.0.

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  For additive traits, these drawn values are used directly as individual offsets; for multiplicative traits, they are transformed with exp() before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale); and for logistic traits, they are used directly as individual offsets for the underlying additive trait.  The default values for these parameters provide a zero-width individual offset distribution that produces no effect.  Note that individual offsets can be set on individuals with setOffsetForTrait() or the <trait-name>Offset property, both on class Individual, so you are not limited to this built-in mechanism for drawing individual offsets; it is just provided for convenience.

-

Finally, the directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

-

The use of the initializeTrait() function is optional.  If it is not called, a new Trait object will be created automatically, with a name generated from the species name plus a "T"; typically, then, the name is simT, except in multispecies models.  This default trait is configured to be multiplicative, with default values for the other parameters except directFitnessEffect, which is T for the default trait.  This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.2.  The creation of the default trait occurs as a side effect of the first call to initializeMutationType(), if initializeTrait() has not already been called.

+

The directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

+

Finally, the baselineAccumulation parameter specifies the behavior of the trait when fixed mutations are turned into Substitution objects (see section 1.5.2).  If baselineAccumulation is F, no special action is taken upon substitution, and so the effect of the substituted mutation will no longer be present; this was the behavior of SLiM prior to SLiM 5.2, and remains the behavior of the default trait for backward compatibility.  If baselineAccumulation is T, the effect of the substituted mutation will be combined (multiplicatively or additively, according to the trait type) into the trait’s baseline offset.  As a result, the trait values of individuals should not change across a substitution, because the effect that used to come from the mutation will now come from the baseline offset.  (If a mutationEffect() callback used to modify that effect, however, that modification will no longer occur, since mutationEffect() callbacks are not called for substitutions; in this case, it might be desirable to prevent substitution with MutationType’s convertToSubstitution property.)  Allowing baseline accumulation is usually desirable; it allows substitution to occur safely even in nonWF models (if convertToSubstitution is set to T), which can make such models more efficient.

+

The use of the initializeTrait() function is optional.  If it is not called, a new Trait object will be created automatically, with a name generated from the species name plus a "T"; typically, then, the name is simT, except in multispecies models.  This default trait is configured to be multiplicative, with default values for the other parameters except directFitnessEffect, which is T for the default trait, and baselineAccumulation, which is F for the default trait.  This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.2.  The creation of the default trait occurs as a side effect of the first call to initializeMutationType(), if initializeTrait() has not already been called.

(void)initializeTreeSeq([logical$ recordMutations = T], [Nif$ simplificationRatio = NULL], [Ni$ simplificationInterval = NULL], [logical$ checkCoalescence = F], [logical$ runCrosschecks = F], [logical$ retainCoalescentOnly = T], [Ns$ timeUnit = NULL])

Configure options for tree sequence recording.  Calling this function turns on tree sequence recording, as a side effect, for later reconstruction of the simulation’s evolutionary dynamics; if you do not want tree sequence recording to be enabled, do not call this function.  Note that tree-sequence recording internally uses SLiM’s “pedigree tracking” feature to uniquely identify individuals and haplosomes; however, if you want to use pedigree tracking in your script you must still enable it yourself with initializeSLiMOptions(keepPedigrees=T).  A separate tree sequence will be recorded for each chromosome in the simulation, as configured with initializeChromosome().

The recordMutations flag controls whether information about individual mutations is recorded or not.  Such recording takes time and memory, and so can be turned off if only the tree sequence itself is needed, but it is turned on by default since mutation recording is generally useful.

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 72b3113c..94104a86 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -14999,7 +14999,31 @@ Note that this method is only for use in nonWF models, in which migration is man \f1\fs22 properties\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\i0\fs18 \cf2 baselineOffset <\'96> (float$)\ +\f3\i0\fs18 \cf2 baselineAccumulation => (logical$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 A +\f3\fs18 logical +\f4\fs20 flag indicating whether the trait accumulates the effects of substitutions into its baseline offset or not. This governs what happens when a mutation that has fixed is converted into a Substitution object, as controlled by +\f3\fs18 MutationType +\f4\fs20 \'92s +\f3\fs18 convertToSubstitution +\f4\fs20 property. If +\f3\fs18 baselineAccumulation +\f4\fs20 is +\f3\fs18 T +\f4\fs20 , the effect of the substituted mutation on this trait is combined (additively or multiplicatively) into the baseline offset of this trait, such that the mutation\'92s effect continues to be present in the simulation even though it has been substituted. If +\f3\fs18 F +\f4\fs20 , this does not occur and so the effect of the substituted mutation on this trait will no longer be present (unless it is handled in script in some way). This flag is controlled by the +\f3\fs18 baselineAccumulation +\f4\fs20 parameter to +\f3\fs18 initializeTrait() +\f4\fs20 ; for the default trait, it is always +\f3\fs18 F +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 baselineOffset <\'96> (float$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 The baseline offset for the trait. This value is combined \'96 multiplicatively for multiplicative traits, additively for additive and logistic traits \'96 with all other effects that influence the trait. This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.\ @@ -15019,7 +15043,13 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 T \f4\fs20 , the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component. If \f3\fs18 F -\f4\fs20 , the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a \'93fitness function\'94), or the trait might have other effects that are not obviously related to fitness at all.\ +\f4\fs20 , the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a \'93fitness function\'94), or the trait might have other effects that are not obviously related to fitness at all. This flag is controlled by the +\f3\fs18 defaultFitnessEffect +\f4\fs20 parameter to +\f3\fs18 initializeTrait() +\f4\fs20 ; for the default trait, it is always +\f3\fs18 T +\f4\fs20 .\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 index => (integer$)\ diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 197737b6..97043612 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -1495,7 +1495,7 @@ The \f2\fs20 , SLiMgui will choose a default color.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f1\fs18 \cf2 (object$)initializeTrait(string$\'a0name, string$\'a0type, [Nf$\'a0baselineOffset\'a0=\'a0NULL], [float$\'a0individualOffsetMean\'a0=\'a00.0], [float$\'a0individualOffsetSD\'a0=\'a00.0], [logical$\'a0directFitnessEffect\'a0=\'a0F])\ +\f1\fs18 \cf2 (object$)initializeTrait(string$\'a0name, string$\'a0type, [Nf$\'a0baselineOffset\'a0=\'a0NULL], [float$\'a0individualOffsetMean\'a0=\'a00.0], [float$\'a0individualOffsetSD\'a0=\'a00.0], [logical$\'a0directFitnessEffect\'a0=\'a0F], [logical$\'a0baselineAccumulation\'a0=\'a0T])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f2\fs20 \cf2 Calling this function, added in SLiM 5.2, configures a phenotypic trait in the species being initialized. The new @@ -1540,8 +1540,7 @@ The \f2\fs20 object to be referenced simply as \f1\fs18 height \f2\fs20 .\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 The +The \f1\fs18 type \f2\fs20 parameter gives the type of trait to be created, as a \f1\fs18 string @@ -1583,8 +1582,7 @@ The \f2\fs20 property, both on class \f1\fs18 Individual \f2\fs20 , so you are not limited to this built-in mechanism for drawing individual offsets; it is just provided for convenience.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 Finally, the +The \f1\fs18 directFitnessEffect \f2\fs20 parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual. This will typically be \f1\fs18 T @@ -1597,6 +1595,32 @@ The \f2\fs20 in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function. It would also be \f1\fs18 F \f2\fs20 for any trait that affects an aspect of the individual other than fitness \'96 dispersal distance, for example, or aggression.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 Finally, the +\f1\fs18 baselineAccumulation +\f2\fs20 parameter specifies the behavior of the trait when fixed mutations are turned into +\f1\fs18 Substitution +\f2\fs20 objects (see section 1.5.2). If +\f1\fs18 baselineAccumulation +\f2\fs20 is +\f1\fs18 F +\f2\fs20 , no special action is taken upon substitution, and so the effect of the substituted mutation will no longer be present; this was the behavior of SLiM prior to SLiM 5.2, and remains the behavior of the default trait for backward compatibility. If +\f1\fs18 baselineAccumulation +\f2\fs20 is +\f1\fs18 T +\f2\fs20 , the effect of the substituted mutation will be combined (multiplicatively or additively, according to the trait type) into the trait\'92s baseline offset. As a result, the trait values of individuals should not change across a substitution, because the effect that used to come from the mutation will now come from the baseline offset. (If a +\f1\fs18 mutationEffect() +\f2\fs20 callback used to modify that effect, however, that modification will no longer occur, since +\f1\fs18 mutationEffect() +\f2\fs20 callbacks are not called for substitutions; in this case, it might be desirable to prevent substitution with +\f1\fs18 MutationType +\f2\fs20 \'92s +\f1\fs18 convertToSubstitution +\f2\fs20 property.) Allowing baseline accumulation is usually desirable; it allows substitution to occur safely even in nonWF models (if +\f1\fs18 convertToSubstitution +\f2\fs20 is set to +\f1\fs18 T +\f2\fs20 ), which can make such models more efficient.\ The use of the \f1\fs18 initializeTrait() \f2\fs20 function is optional. If it is not called, a new @@ -1609,6 +1633,10 @@ The use of the \f1\fs18 directFitnessEffect \f2\fs20 , which is \f1\fs18 T +\f2\fs20 for the default trait, and +\f1\fs18 baselineAccumulation +\f2\fs20 , which is +\f1\fs18 F \f2\fs20 for the default trait. This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.2. The creation of the default trait occurs as a side effect of the first call to \f1\fs18 initializeMutationType() \f2\fs20 , if diff --git a/VERSIONS b/VERSIONS index bf6cb551..085af180 100644 --- a/VERSIONS +++ b/VERSIONS @@ -168,6 +168,7 @@ multitrait branch: for initializeMutationType() and initializeMutationTypeNuc(), change the "dominanceCoeff" parameter to "defaultDominance" switch over to using a lognormal distribution for the default individual offset distribution for multiplicative traits add a third trait type, "logistic" or "l", for modeling traits that represent a probability such as a disease risk + add initializeTrait() parameter [logical$ baselineAccumulation = T] to control, on a per-trait bases, the accumulation of the effects of fixed (substituted) mutations into baseline offsets version 5.1 (Eidos version 4.1): diff --git a/core/community_eidos.cpp b/core/community_eidos.cpp index 3e4eb728..a2407af2 100644 --- a/core/community_eidos.cpp +++ b/core/community_eidos.cpp @@ -125,7 +125,7 @@ const std::vector *Community::ZeroTickFunctionSignat ->AddIntString_S("id")->AddNumeric_S("defaultDominance")->AddString_OSN("distributionType", gStaticEidosValueNULL)->AddEllipsis()); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeRecombinationRate, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric("rates")->AddInt_ON("ends", gStaticEidosValueNULL)->AddString_OS("sex", gStaticEidosValue_StringAsterisk)); - sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeTrait, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class, "SLiM"))->AddString_S("name")->AddString_S("type")->AddFloat_OSN("baselineOffset", gStaticEidosValueNULL)->AddFloat_OS("individualOffsetMean", gStaticEidosValue_Float0)->AddFloat_OS("individualOffsetSD", gStaticEidosValue_Float0)->AddLogical_OS("directFitnessEffect", gStaticEidosValue_LogicalF)); + sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeTrait, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Trait_Class, "SLiM"))->AddString_S("name")->AddString_S("type")->AddFloat_OSN("baselineOffset", gStaticEidosValueNULL)->AddFloat_OS("individualOffsetMean", gStaticEidosValue_Float0)->AddFloat_OS("individualOffsetSD", gStaticEidosValue_Float0)->AddLogical_OS("directFitnessEffect", gStaticEidosValue_LogicalF)->AddLogical_OS("baselineAccumulation", gStaticEidosValue_LogicalT)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeChromosome, nullptr, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_Chromosome_Class, "SLiM"))->AddInt_S("id")->AddInt_OSN("length", gStaticEidosValueNULL)->AddString_OS("type", gStaticEidosValue_StringA)->AddString_OSN("symbol", gStaticEidosValueNULL)->AddString_OSN("name", gStaticEidosValueNULL)->AddInt_OS("mutationRuns", gStaticEidosValue_Integer0)); sim_0_signatures_.emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature(gStr_initializeGeneConversion, nullptr, kEidosValueMaskVOID, "SLiM")) ->AddNumeric_S("nonCrossoverFraction")->AddNumeric_S("meanLength")->AddNumeric_S("simpleConversionFraction")->AddNumeric_OS("bias", gStaticEidosValue_Integer0)->AddLogical_OS("redrawLengthsOnFailure", gStaticEidosValue_LogicalF)); diff --git a/core/haplosome.cpp b/core/haplosome.cpp index 239c9e66..294bdb7e 100644 --- a/core/haplosome.cpp +++ b/core/haplosome.cpp @@ -4465,6 +4465,8 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_removeMutations(EidosGlobalStringID Mutation *mut = mutations_data[value_index]; Substitution *sub = new Substitution(*mut, tick); + species->DoBaselineAccumulationForSubstitution(sub); + // TREE SEQUENCE RECORDING // When doing tree recording, we additionally keep all fixed mutations (their ids) in a multimap indexed by their position // This allows us to find all the fixed mutations at a given position quickly and easily, for calculating derived states diff --git a/core/individual.cpp b/core/individual.cpp index 657e01c0..ca848682 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -6681,6 +6681,11 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv // necessary, for each individual, and the pure neutral trait is then removed from the trait indices so it // takes no further effort downstream. This saves a calculation pass through the mutation list for the trait. + // BCH 1/26/2026: Note that if the baseline offset is neutral and we know that all individual offsets are + // zero, we don't really need to assign trait values here, as long as we skip using them elsewhere. That + // seems architecturally complex, and the savings would probably be small since there are only two reads and + // one write per individual here; the amount of work that could be avoided doesn't really seem large. + slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) diff --git a/core/individual.h b/core/individual.h index 0dc36d48..6c65628e 100644 --- a/core/individual.h +++ b/core/individual.h @@ -163,7 +163,8 @@ class Individual : public EidosDictionaryUnretained // Per-trait information: trait offsets, trait values. If the species has 0 traits, the pointer is // nullptr; if 1 trait, it points to trait_info_0_ for memory locality and to avoid mallocs; if 2+ - // trait, it points to an OWNED malloced buffer. + // trait, it points to an OWNED malloced buffer. BCH 1/26/2026: optimizing the case where we have + // a single trait and trait_info_ thus points to trait_info_0_ does not seem worthwhile at this time. IndividualTraitInfo trait_info_0_; IndividualTraitInfo *trait_info_; diff --git a/core/population.cpp b/core/population.cpp index 52ea44b9..68388d95 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -7682,6 +7682,7 @@ void Population::RemoveAllFixedMutations(void) Mutation *mut_to_remove = mut_block_ptr + fixed_mutation_accumulator[i]; Substitution *sub = new Substitution(*mut_to_remove, tick); + species_.DoBaselineAccumulationForSubstitution(sub); treeseq_substitutions_map_.emplace(mut_to_remove->position_, sub); substitutions_.emplace_back(sub); } @@ -7694,6 +7695,7 @@ void Population::RemoveAllFixedMutations(void) Mutation *mut_to_remove = mut_block_ptr + fixed_mutation_accumulator[i]; Substitution *sub = new Substitution(*mut_to_remove, tick); + species_.DoBaselineAccumulationForSubstitution(sub); substitutions_.emplace_back(sub); } } @@ -8299,6 +8301,8 @@ void Population::PrintAllBinary(std::ostream &p_out, bool p_output_spatial_posit int8_t nucleotide = substitution_ptr->nucleotide_; // Write a tag indicating we are starting a new substitution + // FIXME MULTITRAIT: we need to write the baseline offset into the file header, so that + // baseline accumulation is preserved correctly across a round-trip int32_t substitution_start_tag = 0xFFFF0003; p_out.write(reinterpret_cast(&substitution_start_tag), sizeof substitution_start_tag); diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index dcc84a6c..b89ae4af 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1195,6 +1195,7 @@ const std::string &gStr_initializeSLiMModelType = EidosRegisteredString("initial const std::string &gStr_initializeInteractionType = EidosRegisteredString("initializeInteractionType", gID_initializeInteractionType); // mostly property names +const std::string &gStr_baselineAccumulation = EidosRegisteredString("baselineAccumulation", gID_baselineAccumulation); const std::string &gStr_baselineOffset = EidosRegisteredString("baselineOffset", gID_baselineOffset); const std::string &gStr_individualOffsetMean = EidosRegisteredString("individualOffsetMean", gID_individualOffsetMean); const std::string &gStr_individualOffsetSD = EidosRegisteredString("individualOffsetSD", gID_individualOffsetSD); diff --git a/core/slim_globals.h b/core/slim_globals.h index 48355f8c..4686ab24 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -802,6 +802,7 @@ extern const std::string &gStr_initializeSLiMModelType; extern const std::string &gStr_initializeInteractionType; //extern const std::string &gStr_type; now gEidosStr_type +extern const std::string &gStr_baselineAccumulation; extern const std::string &gStr_baselineOffset; extern const std::string &gStr_individualOffsetMean; extern const std::string &gStr_individualOffsetSD; @@ -1298,6 +1299,7 @@ enum _SLiMGlobalStringID : int { gID_initializeSLiMModelType, gID_initializeInteractionType, + gID_baselineAccumulation, gID_baselineOffset, gID_individualOffsetMean, gID_individualOffsetSD, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index aefad896..cbdde40f 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1707,17 +1707,20 @@ initialize() { initializeSex(); // multiplicative traits - popgen1T = initializeTrait("popgen1T", "m", 1.01, 0.0, 0.01, directFitnessEffect=T); // will have a mix of dominance - popgen2T = initializeTrait("popgen2T", "m", 1.01, 0.0, 0.01, directFitnessEffect=T); // will be independent dominance - n1T = initializeTrait("n1T", "m", directFitnessEffect=T); // neutral with direct effect - n2T = initializeTrait("n2T", "m", directFitnessEffect=F); // neutral with no direct effect + popgen1T = initializeTrait("popgen1T", "m", 1.0, 0.0, 0.01, directFitnessEffect=T); // will have a mix of dominance + popgen2T = initializeTrait("popgen2T", "m", 1.0, 0.0, 0.01, directFitnessEffect=T); // will be independent dominance + n1T = initializeTrait("n1T", "m", directFitnessEffect=T); // neutral with direct effect + n2T = initializeTrait("n2T", "m", directFitnessEffect=F); // neutral with no direct effect // additive traits - quant1T = initializeTrait("quant1T", "a", I1, 0.0, 0.01, directFitnessEffect=F); // will have a mix of dominance - quant2T = initializeTrait("quant2T", "a", I2, 0.0, 0.01, directFitnessEffect=F); // will be independent dominance - n3T = initializeTrait("n3T", "a", directFitnessEffect=F); // non-neutral with no direct effect + quant1T = initializeTrait("quant1T", "a", I1, 0.0, 0.01, directFitnessEffect=F); // will have a mix of dominance + quant2T = initializeTrait("quant2T", "a", I2, 0.0, 0.01, directFitnessEffect=F); // will be independent dominance + n3T = initializeTrait("n3T", "a", directFitnessEffect=F, baselineAccumulation=F); // non-neutral with no direct effect - // quant1T / quant2T will be demanded in script; popgen1T / popgen2T / n1T will be demanded because they have direct effects + // logistic trait + logistic1T = initializeTrait("logistic1T", "l", 0.0, 0.01, 0.01, directFitnessEffect=T); // will have a mix of dominance + + // quant1T / quant2T will be demanded in script; popgen1T / popgen2T / n1T / logistic1T are direct-effect and generate demand // calculation of popgen2T and quant2T should be extremely efficient since they are independent dominance // calculation of n1T should be omitted entirely; SLiM should detect that it is neutral, and not even set phenotype values // n2T and n3T should not be demanded, and should thus never be calculated by SLiM, which we can check in script @@ -1725,24 +1728,26 @@ initialize() { // mutation types initializeMutationType("m1", 0.4, "f", 0.0); // neutral for all traits - initializeMutationType("m2", 0.4, "e", 0.001); // beneficial for the popgen traits + initializeMutationType("m2", 0.4, "e", 0.05); // beneficial for the popgen traits m2.setEffectSizeDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits m2.setEffectSizeDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits + m2.setEffectSizeDistributionForTrait(c(logistic1T), "n", -0.05, 0.1); // biased, wide normal DES for the logistic trait - initializeMutationType("m3", 0.4, "g", -0.001, 1.0); // deleterious for the popgen traits + initializeMutationType("m3", 0.4, "g", -0.05, 1.0); // deleterious for the popgen traits m3.setEffectSizeDistributionForTrait(c(n1T, n2T), "f", 0.0); // neutral DES for the neutral traits m3.setEffectSizeDistributionForTrait(c(quant1T, quant2T), "n", 0.0, 0.1); // unbiased normal DES for the additive traits + m3.setEffectSizeDistributionForTrait(c(logistic1T), "n", -0.05, 0.1); // biased, wide normal DES for the logistic trait c(m2,m3).setEffectSizeDistributionForTrait(n3T, "n", -5.0, 0.5); // very biased and wide DES for n3T - // set up independent dominance for popgen2T and quant2T; note that setting this for m1 should be unnecessary (it is neutral) - c(m1,m2,m3).setDefaultDominanceForTrait(c(popgen2T, quant2T), NAN); + // set up independent dominance for popgen2T and quant2T; note that setting this for m1 is unnecessary (it is neutral) + c(m2,m3).setDefaultDominanceForTrait(c(popgen2T, quant2T), NAN); - // prevent converting to substitutions for m2 and m3; once shifting the baseline offset is supported, this will not be needed - c(m2,m3).convertToSubstitution = F; + // log information about m2 and m3 mutations, for comparison of initial versus final distributions of trait metrics + c(m2,m3).logMutationData(T, trait=NULL, effectSize=T, dominance=T); initializeGenomicElementType("g1", m1, 1.0); // neutral - initializeGenomicElementType("g2", 1:3, c(2, 1, 1)); // mixture + initializeGenomicElementType("g2", 1:3, c(3, 1, 2)); // mixture ids = 1:5; symbols = c(1, 2, "X", "Y", "MT"); @@ -1763,16 +1768,20 @@ initialize() { } } -// set random dominance effects for the popgen1T and quant1T traits -// other effects are generated as specified by the mutation type DES mutation(m2) { + // set random dominance effects for the popgen1T and quant1T and logistic1TDominance traits + // other effects are generated as specified by the mutation type DES mut.popgen1TDominance = runif(1); mut.quant1TDominance = runif(1); + mut.logistic1TDominance = runif(1); return T; } mutation(m3) { + // set random dominance effects for the popgen1T and quant1T and logistic1TDominance traits + // other effects are generated as specified by the mutation type DES mut.popgen1TDominance = runif(1); mut.quant1TDominance = runif(1); + mut.logistic1TDominance = runif(1); return T; } @@ -1780,24 +1789,77 @@ mutation(m3) { sim.addSubpop("p1", 20); } +// tick 7: m2 mutations are completely neutral, m3 are normal +// tick 8: m2 is completely neutral, m3 is neutral for popgen1T and popgen2T, and QTL demand and selection are off +// tick 9: m3 mutations are neutral for popgen1T only + +7:8 mutationEffect(m2) +{ + return NULL; // make neutral +} +8:9 mutationEffect(m3, NULL, "popgen1T") +{ + return NULL; // make neutral +} +8 mutationEffect(m3, NULL, "popgen2T") +{ + return NULL; // make neutral +} + 1: late() { + // make tick 8 neutral + if (sim.cycle == 8) + return; + + // stabilizing selection on quant1T and quant2T, before fitness calculation takes place inds = sim.subpopulations.individuals; - sim.demandPhenotype(NULL, c(sim.quant1T, sim.quant2T)); - fitnessEffect_q1 = dnorm(inds.quant1T, OPT1, SD1) / dnorm(0.0, 0.0, SD1); - fitnessEffect_q2 = dnorm(inds.quant2T, OPT2, SD2) / dnorm(0.0, 0.0, SD2); + + if (community.tick % 2 == 0) + sim.demandPhenotype(NULL, c(sim.quant1T, sim.quant2T)); // the fast way + else + sim.subpopulations.individuals.demandPhenotypeForIndividuals(c(sim.quant1T, sim.quant2T)); // the slow way + + phenotypes_q1 = inds.quant1T; + phenotypes_q2 = inds.quant2T; + + fitnessEffect_q1 = dnorm(phenotypes_q1, OPT1, SD1) / dnorm(0.0, 0.0, SD1); + fitnessEffect_q2 = dnorm(phenotypes_q2, OPT2, SD2) / dnorm(0.0, 0.0, SD2); + inds.fitnessScaling = fitnessEffect_q1 * fitnessEffect_q2; } 2: first() { inds = sim.subpopulations.individuals; - // check that traits that do not require calculation remain uncalculated - //if (!all(isNAN(inds.n1T))) stop("n1T was calculated unnecessarily"); // the smarts for this are not yet implemented! + // check that traits were calculated correctly, or left uncalculated as appropriate + if (!all(inds.n1T == inds.offsetForTrait("n1T"))) stop("n1T was calculated incorrectly"); if (!all(isNAN(inds.n2T))) stop("n2T was calculated unnecessarily"); if (!all(isNAN(inds.n3T))) stop("n3T was calculated unnecessarily"); + if (!all(!isNAN(inds.logistic1T))) stop("logistic1T is NAN"); + if (!all((inds.logistic1T >= 0.0) & (inds.logistic1T <= 1.0))) stop("logistic1T is out of range"); + + // check baseline accumulation, which in on for all traits except n3T + // each substitution shifts the baseline by 1+s (multiplicatively) or 2a (additively) + p1t_subs = product(1 + sim.substitutions.popgen1TEffectSize); + p2t_subs = product(1 + sim.substitutions.popgen2TEffectSize); + n1t_subs = product(1 + sim.substitutions.n1TEffectSize); + n2t_subs = product(1 + sim.substitutions.n2TEffectSize); + q1t_subs = sum(2 * sim.substitutions.quant1TEffectSize); + q2t_subs = sum(2 * sim.substitutions.quant2TEffectSize); + //n3t_subs = sum(2 * sim.substitutions.n3TEffectSize); + l1t_subs = sum(2 * sim.substitutions.logistic1TEffectSize); + + if (!isClose(p1t_subs * 1.0, sim.popgen1T.baselineOffset)) stop("popgen1T baseline is wrong"); + if (!isClose(p2t_subs * 1.0, sim.popgen2T.baselineOffset)) stop("popgen2T baseline is wrong"); + if (!isClose(n1t_subs * 1.0, sim.n1T.baselineOffset)) stop("n1T baseline is wrong"); + if (!isClose(n2t_subs * 1.0, sim.n2T.baselineOffset)) stop("n2T baseline is wrong"); + if (!isClose(q1t_subs + I1, sim.quant1T.baselineOffset)) stop("quant1T baseline is wrong"); + if (!isClose(q2t_subs + I2, sim.quant2T.baselineOffset)) stop("quant2T baseline is wrong"); + if (!(sim.n3T.baselineOffset == 0.0)) stop("n3T baseline is wrong"); + if (!isClose(l1t_subs + 0.0, sim.logistic1T.baselineOffset)) stop("logistic1T baseline is wrong"); } -50 late() { } +100 late() { } )V0G0N"; SLiMAssertScriptSuccess(complex_multi_1); diff --git a/core/species.cpp b/core/species.cpp index a9a701b6..5b8c15e9 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -1460,7 +1460,8 @@ void Species::MakeImplicitTrait(void) /* p_baselineOffset */ 1.0, /* p_individualOffsetMean */ 0.0, /* p_individualOffsetSD */ 0.0, - /* directFitnessEffect */ true); + /* directFitnessEffect */ true, + /* baselineAccumulation */ false); // Add it to our registry; AddTrait() takes its retain count AddTrait(trait); @@ -1606,6 +1607,44 @@ void Species::GetTraitIndicesFromEidosValue(std::vector &tra } } +void Species::DoBaselineAccumulationForSubstitution(Substitution *p_substitution) +{ + // When a new substitution object is created from a mutation, this method is called to do "baseline offset + // accumulation", the moving of the effect of the mutation from the mutation itself (which is no longer + // segregating) into the baseline offsets of traits. This is enabled by default for all traits created + // with initializeTrait(), but disabled for the default trait for backward compatibility. This is not + // terribly lightweight, since we have to loop through all the traits, but substitution is not very common + // so it is not worth trying to optimize with summary flags etc. + for (Trait *trait : traits_) + { + if (trait->HasBaselineAccumulation()) + { + slim_trait_index_t trait_index = trait->Index(); + SubstitutionTraitInfo &trait_info = p_substitution->trait_info_[trait_index]; + + if (trait_info.hemizygous_dominance_coeff_ != (slim_effect_t)1.0) + EIDOS_TERMINATION << "ERROR (Species::DoBaselineAccumulationForSubstitution): baseline accumulation cannot be enabled for trait '" << trait->Name() << "', because a substitution has a hemizygous dominance coefficient other than 1.0 for that trait. The effect of the changed baseline offset would therefore not match the effect of the original mutation, making baseline accumulation invalid. Either (1) hemizygous dominance coefficients must be 1.0 for the trait for all mutations, (2) baseline accumulation must be turned off for the trait, or (3) substitution must be disabled, with convertToSubstitution=F, for all mutation types where the hemizygous dominance coefficient is not 1.0 for the trait." << EidosTerminate(); + + slim_effect_t effect_size = trait_info.effect_size_; + + if (trait->Type() == TraitType::kMultiplicative) + { + // the homozygous effect is 1+s for multiplicative traits + slim_trait_offset_t homozygous_effect = 1.0 + (slim_trait_offset_t)effect_size; + + trait->SetBaselineOffset(trait->BaselineOffset() * homozygous_effect); + } + else + { + // the homozygous effect is 2a for additive and logistic traits + slim_trait_offset_t homozygous_effect = (slim_trait_offset_t)effect_size + (slim_trait_offset_t)effect_size; + + trait->SetBaselineOffset(trait->BaselineOffset() + homozygous_effect); + } + } + } +} + // Input/output #pragma mark - #pragma mark Input/output @@ -3395,6 +3434,8 @@ slim_tick_t Species::_InitializePopulationFromBinaryFile(const char *p_file, Eid EIDOS_TERMINATION << "ERROR (Species::_InitializePopulationFromBinaryFile): mutation type m" << mutation_type_id << " is not nucleotide-based, but a nucleotide value for a mutation of this type was supplied." << EidosTerminate(); // construct the new substitution + // BCH 1/26/2026: note that this does NOT do baseline accumulation, because it is assumed that the + // baseline offset recorded in the file already contains such effects as needed; FIXME MULTITRAIT Substitution *new_substitution = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, selection_coeff, dominance_coeff, subpop_index, origin_tick, fixation_tick, nucleotide); // read its tag, if requested @@ -3735,6 +3776,30 @@ void Species::RunInitializeCallbacks(void) } } + // Defining all traits with baseline accumulation indicates a desire to have substitution occur, with mutational effects accumulated into + // baseline offset values, in at least some cases. If convertToSubstitution is F for all mutation types, indicating a desire that no + // mutation be substitution, a mistake has probably been made that is worth calling to the user's attention. + if (!has_implicit_trait_) + { + bool all_traits_baseline_accumulate = true; + + for (const Trait *trait : traits_) + if (!trait->HasBaselineAccumulation()) + all_traits_baseline_accumulate = false; + + if (all_traits_baseline_accumulate) + { + bool no_traits_substitute = true; + + for (auto mut_type_iter : mutation_types_) + if (mut_type_iter.second->convert_to_substitution_) + no_traits_substitute = false; + + if (no_traits_substitute && !gEidosSuppressWarnings) + SLIM_ERRSTREAM << "#WARNING (Species::RunInitializeCallbacks): all traits are set with baselineAccumulate=T, but there is no mutation type with convertToSubstitution=T, so substitution will probably never occur; this typically indicates a mistake -- either baseline accumulation should be turned off for clarity, or convertToSubstitution should be turned on for at least one mutation type so that substitution occurs in at least some cases. This typically happens when an older SLiM model is converted from using the default trait to explicitly calling initializeTrait()." << std::endl; + } + } + // Ancestral sequence check; this has to wait until after the chromosome has been initialized if (nucleotide_based_) { @@ -10952,6 +11017,8 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapconvert_to_substitution_)) { // this mutation is fixed, and the muttype wants substitutions, so make a substitution + // BCH 1/26/2026: note that this does NOT do baseline accumulation, because it is assumed that the + // baseline offset recorded in the file already contains such effects as needed; FIXME MULTITRAIT // FIXME MULTITRAIT for now I assume the dominance coeff from the mutation type; needs to be added to MutationMetadataRec; likewise hemizygous dominance // FIXME MULTITRAIT this code will also now need to handle the independent dominance case, for which NaN should be in the metadata Substitution *sub = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->DefaultDominanceForTrait(0) /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); // FIXME MULTITRAIT diff --git a/core/species.h b/core/species.h index 26744d31..be78a028 100644 --- a/core/species.h +++ b/core/species.h @@ -529,6 +529,8 @@ class Species : public EidosDictionaryUnretained slim_trait_index_t GetTraitIndexFromEidosValue(EidosValue *trait_value, const std::string &p_method_name); // with a singleton EidosValue void GetTraitIndicesFromEidosValue(std::vector &trait_indices, EidosValue *traits_value, const std::string &p_method_name); + void DoBaselineAccumulationForSubstitution(Substitution *p_substitution); + // Memory usage void TabulateSLiMMemoryUsage_Species(SLiMMemoryUsage_Species *p_usage); // used by outputUsage() and SLiMgui profiling void DeleteAllMutationRuns(void); // for cleanup diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 08870a1d..2838188a 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -1628,7 +1628,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeSpecies(const std::strin return gStaticEidosValueVOID; } -// ********************* (object$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [f$ individualOffsetMean = 0.0], [f$ individualOffsetSD = 0.0], [l$ directFitnessEffect = F]) +// ********************* (object$)initializeTrait(string$ name, string$ type, [Nf$ baselineOffset = NULL], [f$ individualOffsetMean = 0.0], [f$ individualOffsetSD = 0.0], [l$ directFitnessEffect = F], [logical$ baselineAccumulation = T]) // EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string &p_function_name, const std::vector &p_arguments, EidosInterpreter &p_interpreter) { @@ -1657,6 +1657,7 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string EidosValue *individualOffsetMean_value = p_arguments[3].get(); EidosValue *individualOffsetSD_value = p_arguments[4].get(); EidosValue *directFitnessEffect_value = p_arguments[5].get(); + EidosValue *baselineAccumulation_value = p_arguments[6].get(); // name std::string name = name_value->StringAtIndex_NOCAST(0, nullptr); @@ -1738,8 +1739,11 @@ EidosValue_SP Species::ExecuteContextFunction_initializeTrait(const std::string // directFitnessEffect bool directFitnessEffect = directFitnessEffect_value->LogicalAtIndex_NOCAST(0, nullptr); + // baselineAccumulation + bool baselineAccumulation = baselineAccumulation_value->LogicalAtIndex_NOCAST(0, nullptr); + // Set up the new trait object; it gets a retain count on it from EidosDictionaryRetained::EidosDictionaryRetained() - Trait *trait = new Trait(*this, name, type, logistic_post, baselineOffset, individualOffsetMean, individualOffsetSD, directFitnessEffect); + Trait *trait = new Trait(*this, name, type, logistic_post, baselineOffset, individualOffsetMean, individualOffsetSD, directFitnessEffect, baselineAccumulation); EidosValue_SP result_SP = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(trait, gSLiM_Trait_Class)); // Add it to our registry; AddTrait() takes its retain count diff --git a/core/trait.cpp b/core/trait.cpp index ca31e91f..457027dc 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -11,10 +11,11 @@ #include "species.h" -Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect) : +Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect, bool p_baselineAccumulation) : index_(-1), name_(p_name), type_(p_type), logistic_post_(p_logistic_post), individualOffsetMean_(p_individualOffsetMean), individualOffsetSD_(p_individualOffsetSD), - directFitnessEffect_(p_directFitnessEffect), community_(p_species.community_), species_(p_species) + directFitnessEffect_(p_directFitnessEffect), baselineAccumulation_(p_baselineAccumulation), + community_(p_species.community_), species_(p_species) { // offsets must always be finite if (!std::isfinite(p_baselineOffset)) @@ -105,6 +106,14 @@ EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) switch (p_property_id) { // constants + case gID_baselineAccumulation: + { + return (baselineAccumulation_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + } + case gID_directFitnessEffect: + { + return (directFitnessEffect_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); + } case gID_index: { return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(index_)); @@ -148,10 +157,6 @@ EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) { return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)baselineOffset_)); } - case gID_directFitnessEffect: - { - return (directFitnessEffect_ ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); - } case gID_individualOffsetMean: { return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(individualOffsetMean_)); @@ -196,13 +201,6 @@ void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_v return; } - case gID_directFitnessEffect: - { - bool value = p_value.LogicalAtIndex_NOCAST(0, nullptr); - - directFitnessEffect_ = value; - return; - } case gID_individualOffsetMean: { double value = p_value.FloatAtIndex_NOCAST(0, nullptr); @@ -267,6 +265,7 @@ std::vector *Trait_Class::Properties_MUTABLE(void) c properties = new std::vector(*super::Properties_MUTABLE()); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_baselineAccumulation, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_baselineOffset, false, kEidosValueMaskFloat | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_directFitnessEffect, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_index, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); diff --git a/core/trait.h b/core/trait.h index f83babdd..5823b2a6 100644 --- a/core/trait.h +++ b/core/trait.h @@ -78,6 +78,9 @@ class Trait : public EidosDictionaryRetained // this mimics the previous behavior of SLiM, for multiplicative traits bool directFitnessEffect_; + // if true, mutation effects are combined into the baseline offset when the mutation is substituted + bool baselineAccumulation_; + public: Community &community_; @@ -127,7 +130,7 @@ class Trait : public EidosDictionaryRetained Trait& operator=(const Trait&) = delete; // no copying Trait(void) = delete; // no null constructor - explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool directFitnessEffect); + explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect, bool p_baselineAccumulation); ~Trait(void); inline __attribute__((always_inline)) slim_trait_index_t Index(void) const { return index_; } @@ -136,13 +139,15 @@ class Trait : public EidosDictionaryRetained inline __attribute__((always_inline)) bool HasLogisticPostTransform(void) const { return logistic_post_; } inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } - slim_trait_offset_t BaselineOffset(void) const { return baselineOffset_; }; + inline __attribute__((always_inline)) slim_trait_offset_t BaselineOffset(void) const { return baselineOffset_; }; + inline __attribute__((always_inline)) void SetBaselineOffset(slim_trait_offset_t p_baseline) { baselineOffset_ = p_baseline; }; void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetFixed_ and individualOffsetFixedValue_ slim_trait_offset_t _DrawIndividualOffset(void) const; // draws from the distribution defined by individualOffsetMean_ and individualOffsetSD_ inline __attribute__((always_inline)) slim_trait_offset_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } inline __attribute__((always_inline)) bool HasDirectFitnessEffect(void) const { return directFitnessEffect_; } + inline __attribute__((always_inline)) bool HasBaselineAccumulation(void) const { return baselineAccumulation_; } // From 1ffd8ce4db75a405e5e45ff39b063d625a203209 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 28 Jan 2026 14:28:45 -0600 Subject: [PATCH 098/107] fix an autocompletion bug with dynamic properties --- eidos/eidos_class_Object.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/eidos/eidos_class_Object.cpp b/eidos/eidos_class_Object.cpp index 6b8df71d..10113b61 100644 --- a/eidos/eidos_class_Object.cpp +++ b/eidos/eidos_class_Object.cpp @@ -807,6 +807,20 @@ std::vector EidosClass::Properties_TYPE_INTERPRETER( std::sort(properties.begin(), properties.end(), CompareEidosPropertySignatures); + // We got properties from two places: the built-in properties of the class, and dynamic property + // signatures. Dynamic properties actually end up in both places sometimes: they get added to the + // class itself (if execution has proceeded to the point where that happens), AND they get added + // to the list of dynamic properties (by the type interpreter, which can do this even if execution + // has not reached the point where the property actually exists yet). This is good -- it means + // that we know about the signature in two different ways that are valid at different places and + // times. But it also means that we can contain duplicates at this point, so we need to unique. + // The uniquing needs to be done by name; the duplicates are different signature objects. + auto unique_end_iter = std::unique(properties.begin(), properties.end(), + [](const EidosPropertySignature_CSP& a, const EidosPropertySignature_CSP& b) { + return a->property_name_ == b->property_name_; + }); + properties.resize(std::distance(properties.begin(), unique_end_iter)); + return properties; } From 15b82414042e7a51eefba7a6e037a43ebe93cc0e Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 28 Jan 2026 15:02:48 -0600 Subject: [PATCH 099/107] some doc clarifications --- QtSLiM/help/SLiMHelpClasses.html | 3 +++ SLiMgui/SLiMHelpClasses.rtf | 39 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 919d0beb..166ca35e 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -1444,6 +1444,7 @@

A logical flag indicating whether the trait accumulates the effects of substitutions into its baseline offset or not.  This governs what happens when a mutation that has fixed is converted into a Substitution object, as controlled by MutationType’s convertToSubstitution property.  If baselineAccumulation is T, the effect of the substituted mutation on this trait is combined (additively or multiplicatively) into the baseline offset of this trait, such that the mutation’s effect continues to be present in the simulation even though it has been substituted.  If F, this does not occur and so the effect of the substituted mutation on this trait will no longer be present (unless it is handled in script in some way).  This flag is controlled by the baselineAccumulation parameter to initializeTrait(); for the default trait, it is always F.

baselineOffset <–> (float$)

The baseline offset for the trait.  This value is combined – multiplicatively for multiplicative traits, additively for additive and logistic traits – with all other effects that influence the trait.  This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.

+

This property is read-write, and so can be changed.  Doing so usually doesn’t make sense, however, because a change to the baseline offset changes the phenotype of all individuals – not only future offspring, but also individuals that are already alive.  Most of the time, changing the mean of the individual offset distribution for the trait, with the individualOffsetMean property, makes more biological sense since it only affects the trait values of future offspring, leaving the phenotypes of existing individuals unchanged.

Note that for multiplicative traits, all effects are clamped to a minimum of 0.0 as documented in the Trait class.  A baseline offset value set through this property may therefore not be the value used by SLiM or subsequently returned by this property.

directFitnessEffect => (logical$)

A logical flag indicating whether the trait has a direct fitness effect or not.  If T, the trait value for an individual is used as a fitness effect for that individual, implying that the trait should be considered a fitness component.  If F, the trait value is not used directly as a fitness effect; in script, one might compute a fitness effect from the trait value (using a “fitness function”), or the trait might have other effects that are not obviously related to fitness at all.  This flag is controlled by the defaultFitnessEffect parameter to initializeTrait(); for the default trait, it is always T.

@@ -1451,8 +1452,10 @@

The index of the trait in the vector of traits kept by the species.  The first trait defined in a species is at index 0, and subsequent traits count upwards from there.  The index of a trait is often used to refer to the trait, so it is important.  A global constant is defined for every trait, using each trait’s name, that provides the index of each trait, so this property will probably rarely be needed.

individualOffsetMean <–> (float$)

The mean for the normal distribution from which individual offsets are drawn.  (As described in initializeTrait(), for multiplicative traits the drawn values are transformed with exp() before use, so in effect a lognormal distribution is used, and individualOffsetMean is the mean of that lognormal distribution in log space.)  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive and logistic traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetSD property.

+

The individual offset distribution provided by Trait does not need to be used; if you want to set up individual offsets in your own way, simply set individualOffsetSD to 0.0 (so SLiM doesn’t waste time drawing values that won’t be used), and set the individual offset value for each new individual yourself.  Note also that changing individualOffsetMean does not change the individual offsets of existing individuals, since that would not always be desirable; you can set new offsets on individuals yourself if you wish.  See the <trait-name>Offset property and setOffsetForTrait() method of Individual regarding setting individual offset values.

individualOffsetSD <–> (float$)

The standard deviation for the normal distribution from which individual offsets are drawn.  (As described in initializeTrait(), for multiplicative traits the drawn values are transformed with exp() before use, so in effect a lognormal distribution is used, and individualOffsetSD is the standard deviation of that lognormal distribution in log space.)  Individual offsets are combined – multiplicatively for multiplicative traits, additively for additive and logistic traits – with all other effects that influence the trait.  This typically provides random variance in trait values among genetically identical individuals that is often termed “environmental variance” or “developmental noise”.  See also the individualOffsetMean property.

+

The individual offset distribution provided by Trait does not need to be used; if you want to set up individual offsets in your own way, simply set individualOffsetSD to 0.0 (so SLiM doesn’t waste time drawing values that won’t be used), and set the individual offset value for each new individual yourself.  Note also that changing individualOffsetMean does not change the individual offsets of existing individuals, since that would not always be desirable; you can set new offsets on individuals yourself if you wish.  See the <trait-name>Offset property and setOffsetForTrait() method of Individual regarding setting individual offset values.

name => (string$)

The name of the trait, as given to initializeTrait().  The default trait that is provided if no trait is explicitly defined has a name that is the name of the species plus a T; so for a single-species model, the default trait will generally be named simT.  The name of a trait can sometimes be used to refer to the trait, and is visible in SLiMgui.

species => (object<Species>$)

diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 94104a86..42ae3ad4 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -15027,7 +15027,12 @@ Note that this method is only for use in nonWF models, in which migration is man \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 The baseline offset for the trait. This value is combined \'96 multiplicatively for multiplicative traits, additively for additive and logistic traits \'96 with all other effects that influence the trait. This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.\ -Note that for multiplicative traits, all effects are clamped to a minimum of +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 This property is read-write, and so can be changed. Doing so usually doesn\'92t make sense, however, because a change to the baseline offset changes the phenotype of all individuals \'96 not only future offspring, but also individuals that are already alive. Most of the time, changing the mean of the individual offset distribution for the trait, with the +\f3\fs18 individualOffsetMean +\f4\fs20 property, makes more biological sense since it only affects the trait values of future offspring, leaving the phenotypes of existing individuals unchanged.\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 +\cf2 Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 0.0 \f4\fs20 as documented in the \f3\fs18 Trait @@ -15072,6 +15077,22 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f4\fs20 is the mean of that lognormal distribution in log space.) Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive and logistic traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the \f3\fs18 individualOffsetSD \f4\fs20 property.\ +The individual offset distribution provided by +\f3\fs18 Trait +\f4\fs20 does not need to be used; if you want to set up individual offsets in your own way, simply set +\f3\fs18 individualOffsetSD +\f4\fs20 to +\f3\fs18 0.0 +\f4\fs20 (so SLiM doesn\'92t waste time drawing values that won\'92t be used), and set the individual offset value for each new individual yourself. Note also that changing +\f3\fs18 individualOffsetMean +\f4\fs20 does not change the individual offsets of existing individuals, since that would not always be desirable; you can set new offsets on individuals yourself if you wish. See the +\f2\i\fs18 +\f3\i0 Offset +\f4\fs20 property and +\f3\fs18 setOffsetForTrait() +\f4\fs20 method of +\f3\fs18 Individual +\f4\fs20 regarding setting individual offset values.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 individualOffsetSD <\'96> (float$)\ @@ -15086,6 +15107,22 @@ Note that for multiplicative traits, all effects are clamped to a minimum of \f4\fs20 is the standard deviation of that lognormal distribution in log space.) Individual offsets are combined \'96 multiplicatively for multiplicative traits, additively for additive and logistic traits \'96 with all other effects that influence the trait. This typically provides random variance in trait values among genetically identical individuals that is often termed \'93environmental variance\'94 or \'93developmental noise\'94. See also the \f3\fs18 individualOffsetMean \f4\fs20 property.\ +The individual offset distribution provided by +\f3\fs18 Trait +\f4\fs20 does not need to be used; if you want to set up individual offsets in your own way, simply set +\f3\fs18 individualOffsetSD +\f4\fs20 to +\f3\fs18 0.0 +\f4\fs20 (so SLiM doesn\'92t waste time drawing values that won\'92t be used), and set the individual offset value for each new individual yourself. Note also that changing +\f3\fs18 individualOffsetMean +\f4\fs20 does not change the individual offsets of existing individuals, since that would not always be desirable; you can set new offsets on individuals yourself if you wish. See the +\f2\i\fs18 +\f3\i0 Offset +\f4\fs20 property and +\f3\fs18 setOffsetForTrait() +\f4\fs20 method of +\f3\fs18 Individual +\f4\fs20 regarding setting individual offset values.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 \f3\fs18 \cf2 name => (string$)\ From 527fb96352c38c1f7329d4b9c0442f23b4b5a6ec Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Wed, 28 Jan 2026 15:47:03 -0600 Subject: [PATCH 100/107] add a warning when independent dominance is not used and could be --- core/species.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/core/species.cpp b/core/species.cpp index 5b8c15e9..02463e21 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -3800,6 +3800,40 @@ void Species::RunInitializeCallbacks(void) } } + // Defining an additive (or logistic) trait with a default dominance of 0.5 for the trait for all mutation types indicates that + // the trait is probably intended to have independent-dominance effects but has just not been configured in that manner, which + // will result in lower performance; this is probably a mistake that is worth calling to the user's attention. + if (!has_implicit_trait_) + { + for (const Trait *trait : traits_) + { + if (trait->Type() == TraitType::kAdditive) + { + slim_trait_index_t trait_index = trait->Index(); + bool all_muttypes_intermediate_dominance = true; + + for (auto mut_type_iter : mutation_types_) + { + MutationType *muttype = mut_type_iter.second; + + // mutation types with a neutral DES for the trait don't matter for this determination + if (muttype->effect_size_distributions_[trait_index].DES_type_ == DESType::kFixed) + if (muttype->effect_size_distributions_[trait_index].DES_parameters_[0] == 0.0) + continue; + + if (muttype->DefaultDominanceForTrait(trait_index) != (slim_effect_t)0.5) + { + all_muttypes_intermediate_dominance = false; + break; + } + } + + if (all_muttypes_intermediate_dominance && !gEidosSuppressWarnings) + SLIM_ERRSTREAM << "#WARNING (Species::RunInitializeCallbacks): trait '" << trait->Name() << "' is " << (trait->HasLogisticPostTransform() ? "logistic" : "additive") << ", and every non-neutral mutation type has a default dominance of 0.5 for this trait. This suggests that the effects of mutations will exhibit independent dominance for trait '" << trait->Name() << "', but the trait is not configured to exhibit independent dominance; this may result in significantly reduced performance. Using NAN as the default dominance for this trait, for every non-neutral mutation type, would configure the trait to exhibit independent dominance, likely providing a performance improvement with no change in the behavior of the model." << std::endl; + } + } + } + // Ancestral sequence check; this has to wait until after the chromosome has been initialized if (nucleotide_based_) { From 1cc4da9f2100f525b4dbb0ba3032fd0a0f1b0f0e Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 29 Jan 2026 09:54:16 -0600 Subject: [PATCH 101/107] iver renaming for better searchability --- core/mutation_run.cpp | 34 +++++++++++++++++----------------- core/mutation_run.h | 24 ++++++++++++------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/core/mutation_run.cpp b/core/mutation_run.cpp index 297b4fa4..4cc73b94 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -365,7 +365,7 @@ void MutationRun::_RemoveFixedMutations(Mutation *p_mut_block_ptr) #if SLIM_USE_NONNEUTRAL_CACHES() // invalidate the nonneutral mutation cache if (nonneutral_cache_) - nonneutral_cache_->count_ = -1; + nonneutral_cache_->nonneutral_count_ = -1; #endif } } @@ -486,8 +486,8 @@ void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed // because access to the nonneutral mutation buffer is complex and slow, we manage it internally here MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); - int32_t buffer_capacity = nonneutral_cache_->capacity_; - int32_t buffer_count = nonneutral_cache_->count_; + int32_t buffer_capacity = nonneutral_cache_->nonneutral_capacity_; + int32_t buffer_count = nonneutral_cache_->nonneutral_count_; for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { @@ -501,7 +501,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, // expand the buffer and re-fetch our local information about it expand_nonneutral_buffer(species_trait_count); mutation_buffer = nonneutral_mutation_buffer(species_trait_count); - buffer_capacity = nonneutral_cache_->capacity_; + buffer_capacity = nonneutral_cache_->nonneutral_capacity_; } *(mutation_buffer + buffer_count) = mutindex; @@ -509,7 +509,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, } } - nonneutral_cache_->count_ = buffer_count; + nonneutral_cache_->nonneutral_count_ = buffer_count; } void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const @@ -526,8 +526,8 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed // because access to the nonneutral mutation buffer is complex and slow, we manage it internally here MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); - int32_t buffer_capacity = nonneutral_cache_->capacity_; - int32_t buffer_count = nonneutral_cache_->count_; + int32_t buffer_capacity = nonneutral_cache_->nonneutral_capacity_; + int32_t buffer_count = nonneutral_cache_->nonneutral_count_; for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { @@ -544,7 +544,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, // expand the buffer and re-fetch our local information about it expand_nonneutral_buffer(species_trait_count); mutation_buffer = nonneutral_mutation_buffer(species_trait_count); - buffer_capacity = nonneutral_cache_->capacity_; + buffer_capacity = nonneutral_cache_->nonneutral_capacity_; } *(mutation_buffer + buffer_count) = mutindex; @@ -552,7 +552,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, } } - nonneutral_cache_->count_ = buffer_count; + nonneutral_cache_->nonneutral_count_ = buffer_count; } void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const @@ -572,8 +572,8 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed // because access to the nonneutral mutation buffer is complex and slow, we manage it internally here MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); - int32_t buffer_capacity = nonneutral_cache_->capacity_; - int32_t buffer_count = nonneutral_cache_->count_; + int32_t buffer_capacity = nonneutral_cache_->nonneutral_capacity_; + int32_t buffer_count = nonneutral_cache_->nonneutral_count_; for (int32_t bufindex = 0; bufindex < mutation_count_; ++bufindex) { @@ -590,7 +590,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, // expand the buffer and re-fetch our local information about it expand_nonneutral_buffer(species_trait_count); mutation_buffer = nonneutral_mutation_buffer(species_trait_count); - buffer_capacity = nonneutral_cache_->capacity_; + buffer_capacity = nonneutral_cache_->nonneutral_capacity_; } *(mutation_buffer + buffer_count) = mutindex; @@ -606,7 +606,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, // expand the buffer and re-fetch our local information about it expand_nonneutral_buffer(species_trait_count); mutation_buffer = nonneutral_mutation_buffer(species_trait_count); - buffer_capacity = nonneutral_cache_->capacity_; + buffer_capacity = nonneutral_cache_->nonneutral_capacity_; } *(mutation_buffer + buffer_count) = mutindex; @@ -615,7 +615,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, } } - nonneutral_cache_->count_ = buffer_count; + nonneutral_cache_->nonneutral_count_ = buffer_count; } void MutationRun::check_nonneutral_mutation_cache() const @@ -626,9 +626,9 @@ void MutationRun::check_nonneutral_mutation_cache() const if (nonneutral_cache_) { - if (nonneutral_cache_->count_ == -1) + if (nonneutral_cache_->nonneutral_count_ == -1) EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) unvalidated cache." << EidosTerminate(); - if (nonneutral_cache_->count_ > nonneutral_cache_->capacity_) + if (nonneutral_cache_->nonneutral_count_ > nonneutral_cache_->nonneutral_capacity_) EIDOS_TERMINATION << "ERROR (MutationRun::check_nonneutral_mutation_cache): (internal error) cache size exceeds cache capacity." << EidosTerminate(); } } @@ -782,7 +782,7 @@ size_t MutationRun::MemoryUsageForNonneutralCaches(slim_trait_index_t trait_coun { #if SLIM_USE_NONNEUTRAL_CACHES() if (nonneutral_cache_) - return nonneutral_cache_->capacity_ * sizeof(MutationIndex) + sizeof(NonNeutralCache) + trait_count * sizeof(slim_effect_t); + return nonneutral_cache_->nonneutral_capacity_ * sizeof(MutationIndex) + sizeof(NonNeutralCache) + trait_count * sizeof(slim_effect_t); #endif return 0; diff --git a/core/mutation_run.h b/core/mutation_run.h index c77c1826..30f767ce 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -353,8 +353,8 @@ class MutationRun // layout of this struct needs to be known, a species_trait_count value is passed in from outside. This is // kind of weird, but it avoids wasting a ton of storage (and time) on duplicated information. typedef struct _NonNeutralCache { - mutable int32_t capacity_; // the capacity of the nonneutral mutation buffer - mutable int32_t count_; // the number of entries currently used; -1 indicates an invalid cache + mutable int32_t nonneutral_capacity_; // the capacity of the nonneutral mutation buffer + mutable int32_t nonneutral_count_; // the number of entries currently used; -1 indicates an invalid cache slim_effect_t independent_dominance_cache_[]; // one independent-dominance summary per trait in the species // the non-neutral MutationIndex buffer begins after the last per-trait entry in independent_dominance_cache_ } NonNeutralCache; @@ -473,7 +473,7 @@ class MutationRun #if SLIM_USE_NONNEUTRAL_CACHES() if (freed_run->nonneutral_cache_) - freed_run->nonneutral_cache_->count_ = -1; // mark the non-neutral mutation cache as invalid + freed_run->nonneutral_cache_->nonneutral_count_ = -1; // mark the non-neutral mutation cache as invalid #endif // add our new run to the free pool @@ -545,7 +545,7 @@ class MutationRun inline __attribute__((always_inline)) void will_modify_run(void) { #if SLIM_USE_NONNEUTRAL_CACHES() if (nonneutral_cache_) - nonneutral_cache_->count_ = -1; // invalidate the nonneutral cache since the run is changing + nonneutral_cache_->nonneutral_count_ = -1; // invalidate the nonneutral cache since the run is changing #endif } @@ -829,7 +829,7 @@ class MutationRun #if SLIM_USE_NONNEUTRAL_CACHES() // note this method does NOT check external invalidation flags! it tells you only if the mutrun itself knows it is invalid! - inline __attribute__((always_inline)) bool nonneutral_cache_invalid(void) const { return (!nonneutral_cache_ || (nonneutral_cache_->count_ == -1)); } + inline __attribute__((always_inline)) bool nonneutral_cache_invalid(void) const { return (!nonneutral_cache_ || (nonneutral_cache_->nonneutral_count_ == -1)); } inline __attribute__((always_inline)) MutationIndex *nonneutral_mutation_buffer(slim_trait_index_t species_trait_count) const { @@ -849,11 +849,11 @@ class MutationRun if (!nonneutral_cache_) EIDOS_TERMINATION << "ERROR (MutationRun::zero_out_nonneutral_cache): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); - nonneutral_cache_->capacity_ = SLIM_MUTRUN_INITIAL_CAPACITY; + nonneutral_cache_->nonneutral_capacity_ = SLIM_MUTRUN_INITIAL_CAPACITY; } // empty out the current buffer contents - nonneutral_cache_->count_ = 0; + nonneutral_cache_->nonneutral_count_ = 0; } inline __attribute__((always_inline)) void expand_nonneutral_buffer(slim_trait_index_t species_trait_count) const @@ -865,14 +865,14 @@ class MutationRun // we don't just double ad infinitum, because we don't want to use an inordinate amount of memory // adding only 32 capacity at a time is a bit slow, but once we've grown to the high-water size // we should stabilize and not have to realloc any more, so perhaps it's worthwhile... - if (nonneutral_cache_->capacity_ < 128) - nonneutral_cache_->capacity_ <<= 1; // double the number of mutations we can hold + if (nonneutral_cache_->nonneutral_capacity_ < 128) + nonneutral_cache_->nonneutral_capacity_ <<= 1; // double the number of mutations we can hold else - nonneutral_cache_->capacity_ += 32; + nonneutral_cache_->nonneutral_capacity_ += 32; size_t total_size = sizeof(NonNeutralCache) + species_trait_count * sizeof(slim_effect_t) + - nonneutral_cache_->capacity_ * sizeof(MutationIndex); + nonneutral_cache_->nonneutral_capacity_ * sizeof(MutationIndex); nonneutral_cache_ = (NonNeutralCache *)realloc(nonneutral_cache_, total_size); if (!nonneutral_cache_) @@ -897,7 +897,7 @@ class MutationRun MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); *p_mutptr_iter = mutation_buffer; - *p_mutptr_max = mutation_buffer + nonneutral_cache_->count_; + *p_mutptr_max = mutation_buffer + nonneutral_cache_->nonneutral_count_; } #if SLIM_PROFILE_NONNEUTRAL_CACHES() From b81067463690daba3b1f4e9c192cecf3be2dabcc Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 29 Jan 2026 14:48:39 -0600 Subject: [PATCH 102/107] minimize the independent-dominance cache size --- VERSIONS | 1 + core/individual.cpp | 81 ++++++++++++++++++++++------------------- core/individual.h | 2 +- core/mutation_run.cpp | 50 ++++++++++++------------- core/mutation_run.h | 44 +++++++++++----------- core/slim_globals.h | 4 ++ core/species.cpp | 85 +++++++++++++++++++++++++++++++++++++------ core/species.h | 25 +++++++++++++ core/trait.cpp | 10 +++++ core/trait.h | 7 ++-- 10 files changed, 209 insertions(+), 100 deletions(-) diff --git a/VERSIONS b/VERSIONS index 085af180..b122b251 100644 --- a/VERSIONS +++ b/VERSIONS @@ -169,6 +169,7 @@ multitrait branch: switch over to using a lognormal distribution for the default individual offset distribution for multiplicative traits add a third trait type, "logistic" or "l", for modeling traits that represent a probability such as a disease risk add initializeTrait() parameter [logical$ baselineAccumulation = T] to control, on a per-trait bases, the accumulation of the effects of fixed (substituted) mutations into baseline offsets + keep independent dominance caches in MutationRun only for traits that appear to actually be set up for independent dominance version 5.1 (Eidos version 4.1): diff --git a/core/individual.cpp b/core/individual.cpp index ca848682..28ad5b5f 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -6968,7 +6968,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual slim_trait_offset_t trait_baseline_offset = trait->BaselineOffset(); #if DEBUG_TRAIT_DEMAND() - std::cout << " DemandPhenotype_INDIVIDUALS() trait " << trait->Name() << " (" << traitType << ") has baseline offset " << trait_baseline_offset << std::endl; + std::cout << " DemandPhenotype_INDIVIDUALS() trait " << trait->Name() << " (" << trait->UserVisibleType() << ") has baseline offset " << trait_baseline_offset << std::endl; #endif if (traitType == TraitType::kAdditive) @@ -7040,19 +7040,17 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual { slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; - TraitType traitType = trait->Type(); - // Cache a method pointer for incorporating independent dominance effects here too #if SLIM_USE_NONNEUTRAL_CACHES() #if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() - - void (Individual::*_IncorporateEffects_IndependentDominance_TEMPLATED)(Haplosome *haplosome, slim_trait_index_t trait_index) = nullptr; + // Cache a method pointer for incorporating independent dominance effects here too + TraitType traitType = trait->Type(); + void (Individual::*_IncorporateEffects_IndependentDominance_TEMPLATED)(Haplosome *haplosome, slim_trait_index_t trait_index, IndDomCacheIndex inddom_cache_index) = nullptr; if (traitType == TraitType::kAdditive) _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; else _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; - #endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() #endif // SLIM_USE_NONNEUTRAL_CACHES() @@ -7103,8 +7101,10 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual #if DEBUG_TRAIT_DEMAND() independent_dominance_individuals++; #endif - (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome1, trait_index); - (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome2, trait_index); + IndDomCacheIndex inddom_cache_index = species->IndependentDominanceCacheIndexForTraitIndex(trait_index); + + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome1, trait_index, inddom_cache_index); + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome2, trait_index, inddom_cache_index); } else #endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() @@ -7142,25 +7142,26 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual { slim_trait_index_t trait_index = trait_indices[trait_indices_index]; Trait *trait = species->Traits()[trait_index]; - TraitType traitType = trait->Type(); - // Cache a method pointer for incorporating independent dominance effects here too +#if DEBUG_TRAIT_DEMAND() + int total_individuals_recalculated = 0, independent_dominance_individuals = 0; +#endif + #if SLIM_USE_NONNEUTRAL_CACHES() #if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() - - void (Individual::*_IncorporateEffects_IndependentDominance_TEMPLATED)(Haplosome *haplosome, slim_trait_index_t trait_index) = nullptr; + // Cache a method pointer for incorporating independent dominance effects here too + TraitType traitType = trait->Type(); + void (Individual::*_IncorporateEffects_IndependentDominance_TEMPLATED)(Haplosome *haplosome, slim_trait_index_t trait_index, IndDomCacheIndex inddom_cache_index) = nullptr; if (traitType == TraitType::kAdditive) _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; else _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; -#if DEBUG_TRAIT_DEMAND() - int total_individuals_recalculated = 0, independent_dominance_individuals = 0; -#endif - if (trait->is_pure_independent_dominance_now_) { + IndDomCacheIndex inddom_cache_index = species->IndependentDominanceCacheIndexForTraitIndex(trait_index); + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { Individual *ind = individuals_buffer[individual_index]; @@ -7171,7 +7172,7 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) continue; - (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome, trait_index); + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome, trait_index, inddom_cache_index); #if DEBUG_TRAIT_DEMAND() total_individuals_recalculated++; @@ -7281,9 +7282,9 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait - // calculations, so I use a larger threshold here of 1e-5; we'll see how this does in practice. + // calculations, so I use a larger threshold here of 1e-4; we'll see how this does in practice. // The goal is not to check for exact equality, but to find bugs that make calculations incorrect. - if (std::abs(calculated_phenotype - check_phenotype) > (slim_phenotype_t)1e-5) + if (std::abs(calculated_phenotype - check_phenotype) > (slim_phenotype_t)1e-4) EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) phenotype check failed for trait " << species->Traits()[trait_index]->Name() << " (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); } } @@ -7348,7 +7349,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s slim_trait_offset_t trait_baseline_offset = trait->BaselineOffset(); #if DEBUG_TRAIT_DEMAND() - std::cout << " DemandPhenotype_SUBPOP() trait " << trait->Name() << " (" << traitType << ") has baseline offset " << trait_baseline_offset << std::endl; + std::cout << " DemandPhenotype_SUBPOP() trait " << trait->Name() << " (" << trait->UserVisibleType() << ") has baseline offset " << trait_baseline_offset << std::endl; #endif if (traitType == TraitType::kAdditive) @@ -7495,7 +7496,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s #if SLIM_USE_NONNEUTRAL_CACHES() #if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() - void (Individual::*_IncorporateEffects_IndependentDominance_TEMPLATED)(Haplosome *haplosome, slim_trait_index_t trait_index) = nullptr; + void (Individual::*_IncorporateEffects_IndependentDominance_TEMPLATED)(Haplosome *haplosome, slim_trait_index_t trait_index, IndDomCacheIndex inddom_cache_index) = nullptr; if (traitType == TraitType::kAdditive) _IncorporateEffects_IndependentDominance_TEMPLATED = &Individual::_IncorporateEffects_IndependentDominance; @@ -7555,8 +7556,10 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s #if DEBUG_TRAIT_DEMAND() independent_dominance_individuals++; #endif - (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome1, trait_index); - (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome2, trait_index); + IndDomCacheIndex inddom_cache_index = species->IndependentDominanceCacheIndexForTraitIndex(trait_index); + + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome1, trait_index, inddom_cache_index); + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome2, trait_index, inddom_cache_index); } else #endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() @@ -7589,6 +7592,8 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s #if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() if (trait->is_pure_independent_dominance_now_) { + IndDomCacheIndex inddom_cache_index = species->IndependentDominanceCacheIndexForTraitIndex(trait_index); + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) { Individual *ind = individuals_buffer[individual_index]; @@ -7599,7 +7604,7 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s if (!f_force_recalc && !recalc_decisions[individual_index * trait_indices_count + trait_indices_index]) continue; - (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome, trait_index); + (ind->*_IncorporateEffects_IndependentDominance_TEMPLATED)(haplosome, trait_index, inddom_cache_index); #if DEBUG_TRAIT_DEMAND() total_individuals_recalculated++; @@ -7687,9 +7692,9 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait - // calculations, so I use a larger threshold here of 1e-5; we'll see how this does in practice. + // calculations, so I use a larger threshold here of 1e-4; we'll see how this does in practice. // The goal is not to check for exact equality, but to find bugs that make calculations incorrect. - if (std::abs(calculated_phenotype - check_phenotype) > (slim_phenotype_t)1e-5) + if (std::abs(calculated_phenotype - check_phenotype) > (slim_phenotype_t)1e-4) EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_SUBPOP): (internal error) phenotype check failed for trait " << species->Traits()[trait_index]->Name() << " (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); } } @@ -7759,7 +7764,7 @@ void Individual::_IncorporateEffects_Haploid(Species *species, Haplosome *haplos // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome_iter, *haplosome_max; - mutrun->beginend_nonneutral_pointers(&haplosome_iter, &haplosome_max, species->TraitCount()); + mutrun->beginend_nonneutral_pointers(&haplosome_iter, &haplosome_max, species->IndependentDominanceCacheCount()); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome_iter = mutrun->begin_pointer_const(); @@ -7874,8 +7879,8 @@ void Individual::_IncorporateEffects_Diploid(Species *species, Haplosome *haplos // Cache non-neutral mutations and read from the non-neutral buffers const MutationIndex *haplosome1_iter, *haplosome2_iter, *haplosome1_max, *haplosome2_max; - mutrun1->beginend_nonneutral_pointers(&haplosome1_iter, &haplosome1_max, species->TraitCount()); - mutrun2->beginend_nonneutral_pointers(&haplosome2_iter, &haplosome2_max, species->TraitCount()); + mutrun1->beginend_nonneutral_pointers(&haplosome1_iter, &haplosome1_max, species->IndependentDominanceCacheCount()); + mutrun2->beginend_nonneutral_pointers(&haplosome2_iter, &haplosome2_max, species->IndependentDominanceCacheCount()); #else // Read directly from the MutationRun buffers const MutationIndex *haplosome1_iter = mutrun1->begin_pointer_const(); @@ -8191,7 +8196,7 @@ template void Individual::_IncorporateEffects_Diploid(Species #if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() template -void Individual::_IncorporateEffects_IndependentDominance(Haplosome *haplosome, slim_trait_index_t trait_index) +void Individual::_IncorporateEffects_IndependentDominance(Haplosome *haplosome, slim_trait_index_t trait_index, IndDomCacheIndex inddom_cache_index) { #if DEBUG // This method assumes that haplosome is not a null haplosome; the caller needs to guarantee this @@ -8209,16 +8214,16 @@ void Individual::_IncorporateEffects_IndependentDominance(Haplosome *haplosome, const MutationRun *mutrun = haplosome->mutruns_[run_index]; if (f_additiveTrait) - effect_accumulator += (double)mutrun->independent_dominance_cache_for_trait(trait_index); + effect_accumulator += (double)mutrun->independent_dominance_cache_for_cache_index(inddom_cache_index); else - effect_accumulator *= (double)mutrun->independent_dominance_cache_for_trait(trait_index); + effect_accumulator *= (double)mutrun->independent_dominance_cache_for_cache_index(inddom_cache_index); } trait_info_[trait_index].phenotype_ = (slim_phenotype_t)effect_accumulator; } -template void Individual::_IncorporateEffects_IndependentDominance(Haplosome *, slim_trait_index_t); -template void Individual::_IncorporateEffects_IndependentDominance(Haplosome *, slim_trait_index_t); +template void Individual::_IncorporateEffects_IndependentDominance(Haplosome *, slim_trait_index_t, IndDomCacheIndex); +template void Individual::_IncorporateEffects_IndependentDominance(Haplosome *, slim_trait_index_t, IndDomCacheIndex); #endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() #endif // SLIM_USE_NONNEUTRAL_CACHES() @@ -8402,7 +8407,8 @@ void Individual::_Check_IncorporateEffects_Haploid(Species *species, Haplosome * // difference that are indicative of a bug somewhere, rather than checking for an exact match if (trait->is_pure_independent_dominance_now_) { - double independent_dominance_effect = (double)mutrun->independent_dominance_cache_for_trait(trait_index); + IndDomCacheIndex inddom_cache_index = species->IndependentDominanceCacheIndexForTraitIndex(trait_index); + double independent_dominance_effect = (double)mutrun->independent_dominance_cache_for_cache_index(inddom_cache_index); if (std::abs(mutrun_effect_accumulator - independent_dominance_effect) > 1e-5) std::cout << " _Check_IncorporateEffects_Haploid() for trait " << species->Traits()[trait_index]->Name() << " in chromosome " << haplosome->AssociatedChromosome()->Symbol() << " : mutrun effect " << mutrun_effect_accumulator << ", cached independent-dominance effect " << independent_dominance_effect << std::endl; @@ -8834,8 +8840,9 @@ void Individual::_Check_IncorporateEffects_Diploid(Species *species, Haplosome * // difference that are indicative of a bug somewhere, rather than checking for an exact match if (trait->is_pure_independent_dominance_now_) { - double independent_dominance_effect1 = (double)mutrun1->independent_dominance_cache_for_trait(trait_index); - double independent_dominance_effect2 = (double)mutrun2->independent_dominance_cache_for_trait(trait_index); + IndDomCacheIndex inddom_cache_index = species->IndependentDominanceCacheIndexForTraitIndex(trait_index); + double independent_dominance_effect1 = (double)mutrun1->independent_dominance_cache_for_cache_index(inddom_cache_index); + double independent_dominance_effect2 = (double)mutrun2->independent_dominance_cache_for_cache_index(inddom_cache_index); double independent_dominance_effect; if (f_additiveTrait) diff --git a/core/individual.h b/core/individual.h index 6c65628e..03793653 100644 --- a/core/individual.h +++ b/core/individual.h @@ -423,7 +423,7 @@ class Individual : public EidosDictionaryUnretained #if SLIM_USE_NONNEUTRAL_CACHES() #if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() template - void _IncorporateEffects_IndependentDominance(Haplosome *haplosome, slim_trait_index_t trait_index); + void _IncorporateEffects_IndependentDominance(Haplosome *haplosome, slim_trait_index_t trait_index, IndDomCacheIndex inddom_cache_index); #endif #endif diff --git a/core/mutation_run.cpp b/core/mutation_run.cpp index 4cc73b94..78b717ba 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -466,26 +466,26 @@ void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_hal #if SLIM_USE_NONNEUTRAL_CACHES() -void MutationRun::cache_nonneutral_mutations_REGIME_0(slim_trait_index_t species_trait_count) const +void MutationRun::cache_nonneutral_mutations_REGIME_0(IndDomCacheIndex inddom_cache_count) const { // // Regime 0 means there are no genetic effects at all, so we can simply empty the non-neutral cache. // FIXME MULTITRAIT: we want to avoid allocating the nonneutral cache at all, here, if it is not allocated yet // - zero_out_nonneutral_cache(species_trait_count); + zero_out_nonneutral_cache(inddom_cache_count); } -void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const +void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, IndDomCacheIndex inddom_cache_count) const { // // Regime 1 means there are no active mutationEffect() callbacks at all, so neutrality can be assessed // simply by looking at whether the mutation itself is neutral. The mutation type is irrelevant. // - zero_out_nonneutral_cache(species_trait_count); + zero_out_nonneutral_cache(inddom_cache_count); // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed // because access to the nonneutral mutation buffer is complex and slow, we manage it internally here - MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + MutationIndex *mutation_buffer = nonneutral_mutation_buffer(inddom_cache_count); int32_t buffer_capacity = nonneutral_cache_->nonneutral_capacity_; int32_t buffer_count = nonneutral_cache_->nonneutral_count_; @@ -499,8 +499,8 @@ void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, if (buffer_count == buffer_capacity) { // expand the buffer and re-fetch our local information about it - expand_nonneutral_buffer(species_trait_count); - mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + expand_nonneutral_buffer(inddom_cache_count); + mutation_buffer = nonneutral_mutation_buffer(inddom_cache_count); buffer_capacity = nonneutral_cache_->nonneutral_capacity_; } @@ -512,7 +512,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, nonneutral_cache_->nonneutral_count_ = buffer_count; } -void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const +void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, IndDomCacheIndex inddom_cache_count) const { // // Regime 2 means the only mutationEffect() callbacks are (a) constant-effect, (b) neutral (i.e., @@ -521,11 +521,11 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, // of MutationType; if it is set, the mutation is neutral because the callback is known to be // global-neutral. Otherwise, the mutation's neutral flag is reliable. // - zero_out_nonneutral_cache(species_trait_count); + zero_out_nonneutral_cache(inddom_cache_count); // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed // because access to the nonneutral mutation buffer is complex and slow, we manage it internally here - MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + MutationIndex *mutation_buffer = nonneutral_mutation_buffer(inddom_cache_count); int32_t buffer_capacity = nonneutral_cache_->nonneutral_capacity_; int32_t buffer_count = nonneutral_cache_->nonneutral_count_; @@ -542,8 +542,8 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, if (buffer_count == buffer_capacity) { // expand the buffer and re-fetch our local information about it - expand_nonneutral_buffer(species_trait_count); - mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + expand_nonneutral_buffer(inddom_cache_count); + mutation_buffer = nonneutral_mutation_buffer(inddom_cache_count); buffer_capacity = nonneutral_cache_->nonneutral_capacity_; } @@ -555,7 +555,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, nonneutral_cache_->nonneutral_count_ = buffer_count; } -void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const +void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, IndDomCacheIndex inddom_cache_count) const { // // Regime 3 means that there are active mutationEffect() callbacks beyond the constant neutral global @@ -567,11 +567,11 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, // is false, we know the mutation is rendered neutral by a global-neutral callback. And if the test // of subject_to_mutationEffect_callback_ was false, the mutation's neutral flag is reliable. // - zero_out_nonneutral_cache(species_trait_count); + zero_out_nonneutral_cache(inddom_cache_count); // loop through mutations and copy the non-neutral ones into our buffer, resizing as needed // because access to the nonneutral mutation buffer is complex and slow, we manage it internally here - MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + MutationIndex *mutation_buffer = nonneutral_mutation_buffer(inddom_cache_count); int32_t buffer_capacity = nonneutral_cache_->nonneutral_capacity_; int32_t buffer_count = nonneutral_cache_->nonneutral_count_; @@ -588,8 +588,8 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, if (buffer_count == buffer_capacity) { // expand the buffer and re-fetch our local information about it - expand_nonneutral_buffer(species_trait_count); - mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + expand_nonneutral_buffer(inddom_cache_count); + mutation_buffer = nonneutral_mutation_buffer(inddom_cache_count); buffer_capacity = nonneutral_cache_->nonneutral_capacity_; } @@ -604,8 +604,8 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, if (buffer_count == buffer_capacity) { // expand the buffer and re-fetch our local information about it - expand_nonneutral_buffer(species_trait_count); - mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + expand_nonneutral_buffer(inddom_cache_count); + mutation_buffer = nonneutral_mutation_buffer(inddom_cache_count); buffer_capacity = nonneutral_cache_->nonneutral_capacity_; } @@ -636,7 +636,7 @@ void MutationRun::check_nonneutral_mutation_cache() const #if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() template -void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t trait_index, MutationBlock *mutation_block) const +void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t trait_index, IndDomCacheIndex inddom_cache_index, MutationBlock *mutation_block) const { // do internal math using double to avoid numerical error double effect_accumulator = (f_is_additive_trait ? 0.0 : 1.0); // start with neutrality @@ -657,13 +657,13 @@ void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_inde effect_accumulator *= (double)independent_dominance_effect; } - nonneutral_cache_->independent_dominance_cache_[trait_index] = (slim_effect_t)effect_accumulator; + nonneutral_cache_->independent_dominance_cache_[static_cast(inddom_cache_index)] = (slim_effect_t)effect_accumulator; } -template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, MutationBlock *) const; -template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, MutationBlock *) const; -template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, MutationBlock *) const; -template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, MutationBlock *) const; +template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, IndDomCacheIndex, MutationBlock *) const; +template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, IndDomCacheIndex, MutationBlock *) const; +template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, IndDomCacheIndex, MutationBlock *) const; +template void MutationRun::validate_independent_dominance_cache_for_trait(slim_trait_index_t, IndDomCacheIndex, MutationBlock *) const; #endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() #endif // SLIM_USE_NONNEUTRAL_CACHES() diff --git a/core/mutation_run.h b/core/mutation_run.h index 30f767ce..e4423baa 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -345,18 +345,18 @@ class MutationRun // This struct represents the entire non-neutral cache, which can't entirely be described with a C struct due // to variable-length elements. First are the capacity and count for the nonneutral mutation buffer. Then - // come slim_effect_t entries, one per trait in the species, for the independent-dominance cache values for - // all of the mutations in the mutation run. After that is the nonneutral mutation buffer itself: a vector + // come slim_effect_t entries, one per trait in the species for which we have an independent-dominance cache + // (which is not the same as the number of traits). After that is the nonneutral mutation buffer: a vector // of MutationIndex for all of the mutations in the nonneutral cache. This structure should be considered // very private, and be accessed directly only in a few key places inside MutationRun. Note that MutationRun - // doesn't know the number of traits, and so it doesn't know the layout of this struct! In APIs where the - // layout of this struct needs to be known, a species_trait_count value is passed in from outside. This is - // kind of weird, but it avoids wasting a ton of storage (and time) on duplicated information. + // doesn't know the number of traits with independent-dominance caches, and so it doesn't know the layout of + // this struct! In APIs where the layout of this struct needs to be known, an inddom_cache_count value is + // passed in from outside. This is kind of weird, but it is very memory-efficient, which is key here. typedef struct _NonNeutralCache { mutable int32_t nonneutral_capacity_; // the capacity of the nonneutral mutation buffer mutable int32_t nonneutral_count_; // the number of entries currently used; -1 indicates an invalid cache - slim_effect_t independent_dominance_cache_[]; // one independent-dominance summary per trait in the species - // the non-neutral MutationIndex buffer begins after the last per-trait entry in independent_dominance_cache_ + slim_effect_t independent_dominance_cache_[]; // one independent-dominance summary per inddom_cache_count + // the non-neutral MutationIndex buffer begins after the last entry in independent_dominance_cache_ } NonNeutralCache; mutable NonNeutralCache *nonneutral_cache_ = nullptr; // OWNED POINTER: the contents of the nonneutal buffer, or nullptr @@ -831,18 +831,18 @@ class MutationRun // note this method does NOT check external invalidation flags! it tells you only if the mutrun itself knows it is invalid! inline __attribute__((always_inline)) bool nonneutral_cache_invalid(void) const { return (!nonneutral_cache_ || (nonneutral_cache_->nonneutral_count_ == -1)); } - inline __attribute__((always_inline)) MutationIndex *nonneutral_mutation_buffer(slim_trait_index_t species_trait_count) const + inline __attribute__((always_inline)) MutationIndex *nonneutral_mutation_buffer(IndDomCacheIndex inddom_cache_count) const { - return (MutationIndex *)(nonneutral_cache_->independent_dominance_cache_ + species_trait_count); + return (MutationIndex *)(nonneutral_cache_->independent_dominance_cache_ + static_cast(inddom_cache_count)); } - inline __attribute__((always_inline)) void zero_out_nonneutral_cache(slim_trait_index_t species_trait_count) const + inline __attribute__((always_inline)) void zero_out_nonneutral_cache(IndDomCacheIndex inddom_cache_count) const { if (!nonneutral_cache_) { // If we don't have a cache allocated yet, create a buffer with space for all the cache components size_t total_size = sizeof(NonNeutralCache) + - species_trait_count * sizeof(slim_effect_t) + + static_cast(inddom_cache_count) * sizeof(slim_effect_t) + SLIM_MUTRUN_INITIAL_CAPACITY * sizeof(MutationIndex); nonneutral_cache_ = (NonNeutralCache *)malloc(total_size); @@ -856,7 +856,7 @@ class MutationRun nonneutral_cache_->nonneutral_count_ = 0; } - inline __attribute__((always_inline)) void expand_nonneutral_buffer(slim_trait_index_t species_trait_count) const + inline __attribute__((always_inline)) void expand_nonneutral_buffer(IndDomCacheIndex inddom_cache_count) const { #ifdef __clang_analyzer__ assert(nonneutral_cache_->capacity_ > 0); @@ -871,7 +871,7 @@ class MutationRun nonneutral_cache_->nonneutral_capacity_ += 32; size_t total_size = sizeof(NonNeutralCache) + - species_trait_count * sizeof(slim_effect_t) + + static_cast(inddom_cache_count) * sizeof(slim_effect_t) + nonneutral_cache_->nonneutral_capacity_ * sizeof(MutationIndex); nonneutral_cache_ = (NonNeutralCache *)realloc(nonneutral_cache_, total_size); @@ -879,14 +879,14 @@ class MutationRun EIDOS_TERMINATION << "ERROR (MutationRun::expand_nonneutral_buffer): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr); } - void cache_nonneutral_mutations_REGIME_0(slim_trait_index_t species_trait_count) const; - void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const; - void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const; - void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, slim_trait_index_t species_trait_count) const; + void cache_nonneutral_mutations_REGIME_0(IndDomCacheIndex inddom_cache_count) const; + void cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, IndDomCacheIndex inddom_cache_count) const; + void cache_nonneutral_mutations_REGIME_2(Mutation *p_mut_block_ptr, IndDomCacheIndex inddom_cache_count) const; + void cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, IndDomCacheIndex inddom_cache_count) const; void check_nonneutral_mutation_cache() const; - inline __attribute__((always_inline)) void beginend_nonneutral_pointers(const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, slim_trait_index_t species_trait_count) const + inline __attribute__((always_inline)) void beginend_nonneutral_pointers(const MutationIndex **p_mutptr_iter, const MutationIndex **p_mutptr_max, IndDomCacheIndex inddom_cache_count) const { #if DEBUG // All nonneutral caches must be validated ahead of time; see Species::ValidateNonNeutralCaches() @@ -894,7 +894,7 @@ class MutationRun #endif // Return the requested pointers to allow the caller to iterate over the nonneutral mutation buffer - MutationIndex *mutation_buffer = nonneutral_mutation_buffer(species_trait_count); + MutationIndex *mutation_buffer = nonneutral_mutation_buffer(inddom_cache_count); *p_mutptr_iter = mutation_buffer; *p_mutptr_max = mutation_buffer + nonneutral_cache_->nonneutral_count_; @@ -919,11 +919,11 @@ class MutationRun #if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() template - void validate_independent_dominance_cache_for_trait(slim_trait_index_t trait_index, MutationBlock *mutation_block) const; + void validate_independent_dominance_cache_for_trait(slim_trait_index_t trait_index, IndDomCacheIndex inddom_cache_index, MutationBlock *mutation_block) const; - inline __attribute__((always_inline)) slim_effect_t independent_dominance_cache_for_trait(slim_trait_index_t trait_index) const + inline __attribute__((always_inline)) slim_effect_t independent_dominance_cache_for_cache_index(IndDomCacheIndex inddom_cache_index) const { - return nonneutral_cache_->independent_dominance_cache_[trait_index]; + return nonneutral_cache_->independent_dominance_cache_[static_cast(inddom_cache_index)]; } #endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() diff --git a/core/slim_globals.h b/core/slim_globals.h index 4686ab24..aa28c598 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -662,6 +662,10 @@ enum class BoundaryCondition : char { kPeriodic }; +// The enum class is used to isolate independent dominance cache indices from other trait indices, +// producing a compile error whenever one is used in place of the other; they are easy to mix up! +enum class IndDomCacheIndex : slim_trait_index_t {}; + // ******************************************************************************************************************* // diff --git a/core/species.cpp b/core/species.cpp index 02463e21..dbda4127 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -263,7 +263,11 @@ void Species::AutogenerationConfigurationChanged(void) for (Trait *trait : traits_) { trait->trait_all_neutral_mutations_ = true; - trait->trait_all_mutations_independent_dominance_ = true; + + // We longer allow this to get reset back to true, even when the registry is empty. The reason is + // that this flag now needs to be "sticky", to reflect the decision made in RunInitializeCallbacks() + // as to which traits will receive independent-dominance caches. That decision is final. + //trait->trait_all_mutations_independent_dominance_ = true; } } @@ -1099,7 +1103,7 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul } template -int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr, std::vector &pure_independent_dominance_traits) +int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr, __attribute__ ((unused)) std::vector &pure_independent_dominance_traits) { #if DEBUG // Check template flags @@ -1113,7 +1117,6 @@ int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_ // nonneutral mutation caches and then optionally computing cached independent dominance summaries. size_t mutrun_count = p_mutrun_pool.size(); const MutationRun **mutrun_pointers = p_mutrun_pool.data(); - slim_trait_index_t species_trait_count = TraitCount(); #if (SLIMPROFILING == 1) int64_t recached_count; @@ -1130,10 +1133,10 @@ int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_ { switch (f_nonneutral_cache_regime) { - case TraitCalculationRegime::kPureNeutral: mutrun->cache_nonneutral_mutations_REGIME_0(species_trait_count); break; - case TraitCalculationRegime::kNoActiveCallbacks: mutrun->cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr, species_trait_count); break; - case TraitCalculationRegime::kAllNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr, species_trait_count); break; - case TraitCalculationRegime::kNonNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr, species_trait_count); break; + case TraitCalculationRegime::kPureNeutral: mutrun->cache_nonneutral_mutations_REGIME_0(IndependentDominanceCacheCount()); break; + case TraitCalculationRegime::kNoActiveCallbacks: mutrun->cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr, IndependentDominanceCacheCount()); break; + case TraitCalculationRegime::kAllNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr, IndependentDominanceCacheCount()); break; + case TraitCalculationRegime::kNonNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr, IndependentDominanceCacheCount()); break; default: EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) unrecognized regime." << EidosTerminate(); } @@ -1153,11 +1156,12 @@ int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_ for (slim_trait_index_t independent_dominance_trait_index : pure_independent_dominance_traits) { Trait *independent_dominance_trait = traits_[independent_dominance_trait_index]; + IndDomCacheIndex inddom_cache_index = IndependentDominanceCacheIndexForTraitIndex(independent_dominance_trait_index); if (independent_dominance_trait->Type() == TraitType::kAdditive) - mutrun->validate_independent_dominance_cache_for_trait(independent_dominance_trait_index, mutation_block_); + mutrun->validate_independent_dominance_cache_for_trait(independent_dominance_trait_index, inddom_cache_index, mutation_block_); else - mutrun->validate_independent_dominance_cache_for_trait(independent_dominance_trait_index, mutation_block_); + mutrun->validate_independent_dominance_cache_for_trait(independent_dominance_trait_index, inddom_cache_index, mutation_block_); } } #endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() @@ -3842,11 +3846,68 @@ void Species::RunInitializeCallbacks(void) EIDOS_TERMINATION << "ERROR (Species::RunInitializeCallbacks): The chromosome length (" << chromosome->last_position_ + 1 << " base" << (chromosome->last_position_ + 1 != 1 ? "s" : "") << ") does not match the ancestral sequence length (" << chromosome->ancestral_seq_buffer_->size() << " base" << (chromosome->ancestral_seq_buffer_->size() != 1 ? "s" : "") << ")." << EidosTerminate(); } - // notify the species that the mutation autogeneration configuration has changed; rather than - // calling this for each change during initialize(), we call it once here at the end; note - // that this design means that the flags managed by this method will be incorrect until here + // Notify the species that the mutation autogeneration configuration has changed; rather than + // calling this for each change during initialize(), we call it once here at the end. Note + // that this design means that the flags managed by this method will be incorrect until here. AutogenerationConfigurationChanged(); +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + // Now we assess which traits we will turn on independent dominance for, based upon the configuration + // of things at the end of initialize() callbacks. This decision is made just once, right now, and + // takes effect for the entire run. If we decide not to turn on the cache for a given traits, we can + // avoid allocating extra memory per mutation run for it. We decide this per trait, and so we also + // need a mapping from trait index to index into MutationRun's independent dominance cache array, and + // a total count of the number of traits for which independent dominance caches are being kept. Note + // that making a wrong decision here is not catastrophic; if we decide yes and the cache never gets + // used, it's wasted memory but otherwise harmless, and if we decide no but it turns out independent + // dominance does get used for a given trait, we just don't get the optimization for that trait. + for (Trait *trait : traits_) + { + // First of all, the trait must be configured for independent dominance for all non-neutral muttypes. + if (!trait->trait_all_mutations_independent_dominance_) + { +#if DEBUG_TRAIT_DEMAND() + std::cout << "### initialize(): trait '" << trait->Name() << "' is not eligible for independent-dominance caching (not configured for independent dominance)" << std::endl; +#endif + inddom_cache_indices_.push_back(static_cast(-1)); // "the trait at this index has no inddom cache" + continue; + } + + // The trait_all_mutations_independent_dominance_ flag indicates that all *non-neutral* mutations + // for the trait are set up for independent dominance. But if the trait is set up to be entirely + // neutral, trait_all_mutations_independent_dominance_ will be true and yet we need no cached values + // for it; so we check that here as an additional condition. + if (trait->trait_all_neutral_mutations_) + { +#if DEBUG_TRAIT_DEMAND() + std::cout << "### initialize(): trait '" << trait->Name() << "' is not eligible for independent-dominance caching (all muttypes have a neutral DES for it)" << std::endl; +#endif + inddom_cache_indices_.push_back(static_cast(-1)); // "the trait at this index has no inddom cache" + continue; + } + + // This trait will have space allocated for a independent-dominance cache in each MutationRun. +#if DEBUG_TRAIT_DEMAND() + std::cout << "### initialize(): trait '" << trait->Name() << "' IS ELIGIBLE for independent-dominance caching" << std::endl; +#endif + inddom_cache_indices_.push_back(inddom_cache_count_); + inddom_cache_count_ = static_cast(static_cast(inddom_cache_count_) + 1); + } + + std::cout << "### initialize(): " << static_cast(inddom_cache_count_) << " traits will receive independent-dominance cache space"; + if (static_cast(inddom_cache_count_) > 0) + { + std::cout << " : { "; + for (Trait *trait : traits_) + if (static_cast(inddom_cache_indices_[trait->Index()]) != -1) + std::cout << trait->Name() << " "; + std::cout << "}"; + } + std::cout << std::endl; +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() + // always start at cycle 1, regardless of what the starting tick value might be SetCycle(1); diff --git a/core/species.h b/core/species.h index be78a028..4fa7ec38 100644 --- a/core/species.h +++ b/core/species.h @@ -232,6 +232,17 @@ class Species : public EidosDictionaryUnretained TRAIT_NAME_HASH trait_from_name; // NOT OWNED; get a trait from a trait name quickly TRAIT_STRID_HASH trait_from_string_id; // NOT OWNED; get a trait from a string ID quickly +#if SLIM_USE_NONNEUTRAL_CACHES() +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + // Species is in charge of which traits receive independent-dominance caches and which don't, a determination + // made in RunInitializeCallbacks() and never revisited (since nonneutral caches are then configured). + // We keep track of the number of traits being cached (the number of cache slots kept by MutationRun), and + // a mapping from trait value to the index into MutationRun's vector of cache values. + IndDomCacheIndex inddom_cache_count_ = static_cast(0); + std::vector inddom_cache_indices_; +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() + bool mutation_stack_policy_changed_ = true; // when set, the stacking policy settings need to be checked for consistency // SEX ONLY: sex-related instance variables @@ -489,7 +500,21 @@ class Species : public EidosDictionaryUnretained // Validates nonneutral caches for mutation runs in one MutationRunPool. Called by _ValidateNonNeutralCaches(). template int64_t _ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_mutrun_pool, Mutation *p_mut_block_ptr, std::vector &pure_independent_dominance_traits); + +#if SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + inline __attribute__((always_inline)) IndDomCacheIndex IndependentDominanceCacheCount(void) const { return inddom_cache_count_; } + inline __attribute__((always_inline)) IndDomCacheIndex IndependentDominanceCacheIndexForTraitIndex(slim_trait_index_t trait_index) { + IndDomCacheIndex inddom_cache_index = inddom_cache_indices_[trait_index]; +#if DEBUG + if (inddom_cache_index == static_cast(-1)) + EIDOS_TERMINATION << "ERROR (Species::IndependentDominanceCacheIndexForTraitIndex): (internal error) no independent dominance cache for trait." << EidosTerminate(); #endif + return inddom_cache_index; + } +#else // !SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() + inline __attribute__((always_inline)) slim_trait_index_t IndependentDominanceCacheCount(void) const { return 0; } +#endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() +#endif // SLIM_USE_NONNEUTRAL_CACHES() // Chromosome configuration and access inline __attribute__((always_inline)) const std::vector &Chromosomes(void) { return chromosomes_; } diff --git a/core/trait.cpp b/core/trait.cpp index 457027dc..b1ea5986 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -67,6 +67,16 @@ Trait::~Trait(void) //EIDOS_ERRSTREAM << "Trait::~Trait" << std::endl; } +std::string Trait::UserVisibleType(void) const +{ + if (HasLogisticPostTransform()) + return "logistic"; + else if (Type() == TraitType::kAdditive) + return "additive"; + else + return "multiplicative"; +} + const EidosClass *Trait::Class(void) const { return gSLiM_Trait_Class; diff --git a/core/trait.h b/core/trait.h index 5823b2a6..24f4bec8 100644 --- a/core/trait.h +++ b/core/trait.h @@ -91,11 +91,11 @@ class Trait : public EidosDictionaryRetained // OPTIMIZATION FLAGS - // this is set to false if any mutation has a non-neutral effect on this trait + // This is set to false if any mutation has a non-neutral effect on this trait. mutable bool trait_all_neutral_mutations_ = true; - // this is set to false if any mutation has a non-neutral effect on this trait that - // is not designated as "independent dominance"; neutral effects don't matter + // This is set to false if any mutation has a non-neutral effect on this trait that is not designated as + // "independent dominance"; neutral effects don't matter. This flag is "sticky" at false, permanently. mutable bool trait_all_mutations_independent_dominance_ = true; // Optimization flags set up by Species::PrepareForTraitCalculations() and valid only subsequent to that call. @@ -138,6 +138,7 @@ class Trait : public EidosDictionaryRetained inline __attribute__((always_inline)) TraitType Type(void) const { return type_; } inline __attribute__((always_inline)) bool HasLogisticPostTransform(void) const { return logistic_post_; } inline __attribute__((always_inline)) const std::string &Name(void) const { return name_; } + std::string UserVisibleType(void) const; inline __attribute__((always_inline)) slim_trait_offset_t BaselineOffset(void) const { return baselineOffset_; }; inline __attribute__((always_inline)) void SetBaselineOffset(slim_trait_offset_t p_baseline) { baselineOffset_ = p_baseline; }; From 2095f491df67dc2f4e2d137ed5df5c850dfd5c30 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Thu, 29 Jan 2026 15:14:09 -0600 Subject: [PATCH 103/107] fix CI build error --- core/mutation_run.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/mutation_run.h b/core/mutation_run.h index e4423baa..5ac5427f 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -906,8 +906,8 @@ class MutationRun { *p_mutation_count += mutation_count_; - if (nonneutral_cache_->count_ != -1) - *p_nonneutral_count += nonneutral_cache_->count_; + if (nonneutral_cache_->nonneutral_count_ != -1) + *p_nonneutral_count += nonneutral_cache_->nonneutral_count_; } #endif // SLIM_PROFILE_NONNEUTRAL_CACHES() From 91df5007c88ffdbc4cac68095be81f8cfcd50722 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 1 Feb 2026 10:49:48 -0600 Subject: [PATCH 104/107] refine optimizations for neutral traits --- EidosScribe/EidosHelpController.mm | 32 +- QtSLiM/QtSLiMHelpWindow.cpp | 38 +- QtSLiM/help/SLiMHelpCallbacks.html | 2 +- QtSLiM/help/SLiMHelpClasses.html | 2 +- QtSLiM/help/SLiMHelpFunctions.html | 2 +- SLiMgui/SLiMHelpCallbacks.rtf | 10 +- SLiMgui/SLiMHelpClasses.rtf | 8 +- SLiMgui/SLiMHelpFunctions.rtf | 11 +- core/genomic_element_type.cpp | 4 +- core/individual.cpp | 66 +++- core/individual.h | 12 +- core/mutation.cpp | 11 +- core/mutation_run.cpp | 34 +- core/mutation_run.h | 87 +++-- core/mutation_type.cpp | 4 +- core/mutation_type.h | 18 +- core/population.cpp | 8 +- core/slim_globals.cpp | 5 + core/slim_globals.h | 14 +- core/slim_test_genetics.cpp | 269 ++++++++++++- core/species.cpp | 588 +++++++++++++++++++++++------ core/species.h | 40 +- core/species_eidos.cpp | 60 +++ core/subpopulation.cpp | 245 ++++++++---- core/subpopulation.h | 14 +- core/trait.cpp | 40 +- core/trait.h | 32 +- 27 files changed, 1260 insertions(+), 396 deletions(-) diff --git a/EidosScribe/EidosHelpController.mm b/EidosScribe/EidosHelpController.mm index 85f5413f..9943f56f 100644 --- a/EidosScribe/EidosHelpController.mm +++ b/EidosScribe/EidosHelpController.mm @@ -698,24 +698,28 @@ - (void)checkDocumentationOfClass:(EidosClass *)classObject for (const EidosPropertySignature_CSP &propertySignature : *classProperties) { - std::string &&connector_string = propertySignature->PropertySymbol(); - NSString *connectorString = [NSString stringWithUTF8String:connector_string.c_str()]; // "<–>" or "=>" NSString *propertyNameString = [NSString stringWithUTF8String:propertySignature->property_name_.c_str()]; - NSString *propertyString = [NSString stringWithFormat:@"%@ %@", propertyNameString, connectorString]; - NSUInteger docIndex = [docProperties indexOfObject:propertyString]; - if (docIndex != NSNotFound) + if (![propertyNameString hasPrefix:@"_"]) { - // If the property is defined in this class doc, consider it documented - [docProperties removeObjectAtIndex:docIndex]; - } - else - { - // If the property is not defined in this class doc, then that is an error unless it is a superclass property - bool isSuperclassProperty = superclassProperties && (std::find(superclassProperties->begin(), superclassProperties->end(), propertySignature) != superclassProperties->end()); + std::string &&connector_string = propertySignature->PropertySymbol(); + NSString *connectorString = [NSString stringWithUTF8String:connector_string.c_str()]; // "<–>" or "=>" + NSString *propertyString = [NSString stringWithFormat:@"%@ %@", propertyNameString, connectorString]; + NSUInteger docIndex = [docProperties indexOfObject:propertyString]; - if (!isSuperclassProperty) - NSLog(@"*** no documentation found for class %@ property %@", classString, propertyString); + if (docIndex != NSNotFound) + { + // If the property is defined in this class doc, consider it documented + [docProperties removeObjectAtIndex:docIndex]; + } + else + { + // If the property is not defined in this class doc, then that is an error unless it is a superclass property + bool isSuperclassProperty = superclassProperties && (std::find(superclassProperties->begin(), superclassProperties->end(), propertySignature) != superclassProperties->end()); + + if (!isSuperclassProperty) + NSLog(@"*** no documentation found for class %@ property %@", classString, propertyString); + } } } diff --git a/QtSLiM/QtSLiMHelpWindow.cpp b/QtSLiM/QtSLiMHelpWindow.cpp index d3e7afac..ed83e12b 100644 --- a/QtSLiM/QtSLiMHelpWindow.cpp +++ b/QtSLiM/QtSLiMHelpWindow.cpp @@ -1001,24 +1001,28 @@ void QtSLiMHelpWindow::checkDocumentationOfClass(EidosClass *classObject) for (const EidosPropertySignature_CSP &propertySignature : *classProperties) { - const std::string &&connector_string = propertySignature->PropertySymbol(); - const std::string &property_name_string = propertySignature->property_name_; - QString property_string = QString::fromStdString(property_name_string) + QString("\u00A0") + QString::fromStdString(connector_string); - int docIndex = docProperties.indexOf(property_string); - - if (docIndex != -1) - { - // If the property is defined in this class doc, consider it documented - docProperties.removeAt(docIndex); - } - else - { - // If the property is not defined in this class doc, then that is an error unless it is a superclass property - bool isSuperclassProperty = superclassProperties && (std::find(superclassProperties->begin(), superclassProperties->end(), propertySignature) != superclassProperties->end()); + const std::string &property_name_string = propertySignature->property_name_; - if (!isSuperclassProperty) - qDebug() << "*** no documentation found for class " << className << " property " << property_string; - } + if ((property_name_string.length() == 0) || (property_name_string[0] != '_')) + { + const std::string &&connector_string = propertySignature->PropertySymbol(); + QString property_string = QString::fromStdString(property_name_string) + QString("\u00A0") + QString::fromStdString(connector_string); + int docIndex = docProperties.indexOf(property_string); + + if (docIndex != -1) + { + // If the property is defined in this class doc, consider it documented + docProperties.removeAt(docIndex); + } + else + { + // If the property is not defined in this class doc, then that is an error unless it is a superclass property + bool isSuperclassProperty = superclassProperties && (std::find(superclassProperties->begin(), superclassProperties->end(), propertySignature) != superclassProperties->end()); + + if (!isSuperclassProperty) + qDebug() << "*** no documentation found for class " << className << " property " << property_string; + } + } } // BCH 1/7/2026: Remove dynamic properties like "EffectSize" from the excess documentation list; they are not expected to have a match diff --git a/QtSLiM/help/SLiMHelpCallbacks.html b/QtSLiM/help/SLiMHelpCallbacks.html index 13d3c083..cf6bbf2d 100644 --- a/QtSLiM/help/SLiMHelpCallbacks.html +++ b/QtSLiM/help/SLiMHelpCallbacks.html @@ -139,7 +139,7 @@

individual The focal individual (an object of class Individual)

subpop The subpopulation in which that individual lives

These may be used in the fitnessEffect() callback to compute a fitness effect that depends upon the state of the focal individual.  The fitness effect for the callback is simply returned as a singleton float value, as usual.

-

More than one fitnessEffect() callback may be defined to operate in the same tick.  Each such callback will provide an independent fitness effect for the focal individual; the results of each fitnessEffect() callback will be multiplied in to the individual’s fitness.  These callbacks will generally be called once per individual in each tick, in an order that is formally undefined.

+

More than one fitnessEffect() callback may be defined to operate in the same tick.  Each such callback will provide an independent fitness effect for the focal individual; the results of each fitnessEffect() callback will be multiplied in to the individual’s fitness.  These callbacks will generally be called once per individual in each tick, in an order that is formally undefined.  Note that if SLiM can determine, from other fitness effects in the model, that the fitness of an individual is 0.0, it may skip calling fitnessEffect() callbacks for that individual (since the callback would not be able to change the fitness value of the individual – zero multiplied by anything is still zero).  It is therefore not a good idea to rely upon fitnessEffect() callbacks always being called, or to rely upon side effects caused by a fitnessEffect() callback.

Beginning in SLiM 3.0, it is also possible to set the fitnessScaling property on a subpopulation to scale the fitness values of every individual in the subpopulation by the same constant amount, or to set the fitnessScaling property on an individual to scale the fitness value of that specific individual.  These scaling factors are multiplied together with all other fitness effects for an individual to produce the individual’s final fitness value.  The fitnessScaling properties of Subpopulation and Individual can often provide similar functionality to fitnessEffect() callbacks with greater efficiency and simplicity.  They are reset to 1.0 in every tick for which a given species is active, immediately after fitness values are calculated, so they only need to be set when a value other than 1.0 is desired.

As with mutationEffect() callbacks, fitnessEffect() callbacks are called at the end of the tick, just before the next tick begins.  Also, as with mutationEffect() callbacks, the order in which fitnessEffect() callbacks are called will be shuffled when randomizeCallbacks is enabled, as it is by default, partially mitigating order-dependency issues.

The fitnessEffect() callback mechanism is quite flexible and useful, although it has been considerably eclipsed by the modern modern and efficient fitnessScaling property mentioned above.  When efficiency is not at a premium, it remains a clear and expressive paradigm for modeling individual-level fitness effects.  The performance penalty paid is often not large, since these callbacks are called only once per individual per tick, whereas a mutationEffect() for a type of mutation that is common in the simulation might be called thousands of times per individual per tick (once per mutation of that type possessed by the focal individual).  The performance penalty typically becomes severe only when the fitnessEffect() callback needs to perform calculations, once per focal individual, that would vectorize well if performed across a whole vector of individuals.  In such cases, fitnessScaling should be used.

diff --git a/QtSLiM/help/SLiMHelpClasses.html b/QtSLiM/help/SLiMHelpClasses.html index 166ca35e..1e476e82 100644 --- a/QtSLiM/help/SLiMHelpClasses.html +++ b/QtSLiM/help/SLiMHelpClasses.html @@ -768,7 +768,7 @@

convertToSubstitution <–> (logical$)

This property governs whether mutations of this mutation type will be converted to Substitution objects when they reach fixation.

In WF models this property is T by default, since conversion to Substitution objects provides large speed benefits; it should be set to F only if necessary, and only on the mutation types for which it is necessary.  This might be needed, for example, if you are using a mutationEffect() callback to implement an epistatic relationship between mutations; a mutation epistatically influencing the fitness of other mutations through a mutationEffect() callback would need to continue having that influence even after reaching fixation, but if the simulation were to replace the fixed mutation with a Substitution object the mutation would no longer be considered in fitness calculations (unless the callback explicitly consulted the list of Substitution objects kept by the simulation).  Other script-defined behaviors in mutationEffect(), interaction(), mateChoice(), modifyChild(), and recombination() callbacks might also necessitate the disabling of substitution for a given mutation type; this is an important consideration to keep in mind.

-

In contrast, for nonWF models this property is F by default, because even mutations with no epistatis or other indirect fitness effects will continue to influence the survival probabilities of individuals.  For nonWF models, only neutral mutation types with no epistasis or other side effects can safely be converted to substitutions upon fixation.  When such a pure-neutral mutation type is defined in a nonWF model, this property should be set to T to tell SLiM that substitution is allowed; this may have very large positive effects on performance, so it is important to remember when modeling background neutral mutations.

+

In contrast, for nonWF models this property is F by default, because even mutations with no epistatis or other indirect fitness effects will continue to influence the survival probabilities of individuals.  For nonWF models, only neutral mutation types with no epistasis or other side effects can safely be converted to substitutions upon fixation.  When such a completely neutral mutation type is defined in a nonWF model, this property should be set to T to tell SLiM that substitution is allowed; this may have very large positive effects on performance, so it is important to remember when modeling background neutral mutations.

SLiM consults this flag at the end of each tick when deciding whether to substitute each fixed mutation.  If this flag is T, all eligible fixed mutations will be converted at the end of the current tick, even if they were previously left unconverted because of the previous value of the flag.  Setting this flag to F will prevent future substitutions, but will not cause any existing Substitution objects to be converted back into Mutation objects.

id => (integer$)

The identifier for this mutation type; for mutation type m3, for example, this is 3.

diff --git a/QtSLiM/help/SLiMHelpFunctions.html b/QtSLiM/help/SLiMHelpFunctions.html index 22cf5e53..d65af464 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -149,7 +149,7 @@

The name parameter gives the name of the new trait.  This may be any (non-empty) string, except that it must not have the same name as another trait in the species, it must be syntactically valid as an Eidos symbol (not containing a space, for example), and it must not conflict with the name of any existing property on the Individual, Species, Mutation, or Substitution classes.  These requirements are necessary because, after the new trait is created, new properties are added to those classes, with the same name as the new trait, for convenience.  The new Individual property allows trait values to be accessed directly through a property; for example, if the new trait is named height, getting and setting an individual’s trait value would be possible through the property individual.height.  The new Species property allows traits themselves to be accessed directly through a property; continuing the previous example, sim.height would provide the Trait object named height.  See the Mutation and Substitution classes for details on the trait-related properties defined for them.  If desired, defineConstant() may also be used to set up a global constant for a trait; for example, defineConstant("height", height) would allow the Trait object to be referenced simply as height.

The type parameter gives the type of trait to be created, as a string value.  This should be either "multiplicative", if the trait value should be the result of multiplying effects together (as in a typical population-genetics model); "additive", if the trait value should be the result of adding effects together (as in a typical quantitative-genetics model); or "logistic", if the trait value should be the result of a logistic transformation of an additive trait value (for modeling a trait that represents a probability, such as a disease risk).  (Because the logistic trait type is based upon an underlying additive trait value that is transformed, it will often be grouped together in this manual as having “additive” effects; this is in reference to that underlying model, prior to the logistic transformation.)  The shorter versions "m", "a", and "l" are also allowed.

The baselineOffset parameter sets the baseline offset for the trait, incorporated (multiplicatively or additively) to the trait value of every individual.  If NULL is passed, the default baseline offset is 1.0 for multiplicative traits, 0.0 for additive and logistic traits, such that the baseline offset has no effect upon the trait value.  Note that for multiplicative traits all effects, including the baseline offset, will be clamped to a minimum of 0.0.

-

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  For additive traits, these drawn values are used directly as individual offsets; for multiplicative traits, they are transformed with exp() before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale); and for logistic traits, they are used directly as individual offsets for the underlying additive trait.  The default values for these parameters provide a zero-width individual offset distribution that produces no effect.  Note that individual offsets can be set on individuals with setOffsetForTrait() or the <trait-name>Offset property, both on class Individual, so you are not limited to this built-in mechanism for drawing individual offsets; it is just provided for convenience.

+

The individualOffsetMean and individualOffsetSD parameters together define a normal distribution from which individual offsets are drawn to provide what is often called “environmental variance” or “developmental noise”.  For additive traits, these drawn values are used directly as individual offsets; for multiplicative traits, they are transformed with exp() before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale); and for logistic traits, they are used directly as individual offsets for the underlying additive trait.  The default values for these parameters provide a zero-width individual offset distribution that produces no effect; for additive and logistic traits, the resulting effect is 0.0, whereas for multiplicative traits it is 1.0 due to the exp() transform.  Note that individual offsets can be set on individuals with setOffsetForTrait() or the <trait-name>Offset property, both on class Individual, so you are not limited to this built-in mechanism for drawing individual offsets; it is just provided for convenience.

The directFitnessEffect parameter specifies whether the final calculated trait value for an individual should be used directly as a fitness effect for that individual.  This will typically be T (the default) in population-genetics models where the product of all mutation effects (1+s or 1+hs for each mutation) is used as the fitness of the individual, but will typically be F in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function.  It would also be F for any trait that affects an aspect of the individual other than fitness – dispersal distance, for example, or aggression.

Finally, the baselineAccumulation parameter specifies the behavior of the trait when fixed mutations are turned into Substitution objects (see section 1.5.2).  If baselineAccumulation is F, no special action is taken upon substitution, and so the effect of the substituted mutation will no longer be present; this was the behavior of SLiM prior to SLiM 5.2, and remains the behavior of the default trait for backward compatibility.  If baselineAccumulation is T, the effect of the substituted mutation will be combined (multiplicatively or additively, according to the trait type) into the trait’s baseline offset.  As a result, the trait values of individuals should not change across a substitution, because the effect that used to come from the mutation will now come from the baseline offset.  (If a mutationEffect() callback used to modify that effect, however, that modification will no longer occur, since mutationEffect() callbacks are not called for substitutions; in this case, it might be desirable to prevent substitution with MutationType’s convertToSubstitution property.)  Allowing baseline accumulation is usually desirable; it allows substitution to occur safely even in nonWF models (if convertToSubstitution is set to T), which can make such models more efficient.

The use of the initializeTrait() function is optional.  If it is not called, a new Trait object will be created automatically, with a name generated from the species name plus a "T"; typically, then, the name is simT, except in multispecies models.  This default trait is configured to be multiplicative, with default values for the other parameters except directFitnessEffect, which is T for the default trait, and baselineAccumulation, which is F for the default trait.  This provides the behavior of SLiM prior to the introduction of multiple traits in SLiM 5.2.  The creation of the default trait occurs as a side effect of the first call to initializeMutationType(), if initializeTrait() has not already been called.

diff --git a/SLiMgui/SLiMHelpCallbacks.rtf b/SLiMgui/SLiMHelpCallbacks.rtf index 40793dd7..8445d02f 100644 --- a/SLiMgui/SLiMHelpCallbacks.rtf +++ b/SLiMgui/SLiMHelpCallbacks.rtf @@ -578,7 +578,15 @@ More than one \f3\fs18 fitnessEffect() \f2\fs22 callback may be defined to operate in the same tick. Each such callback will provide an independent fitness effect for the focal individual; the results of each \f3\fs18 fitnessEffect() -\f2\fs22 callback will be multiplied in to the individual\'92s fitness. These callbacks will generally be called once per individual in each tick, in an order that is formally undefined.\ +\f2\fs22 callback will be multiplied in to the individual\'92s fitness. These callbacks will generally be called once per individual in each tick, in an order that is formally undefined. Note that if SLiM can determine, from other fitness effects in the model, that the fitness of an individual is +\f3\fs18 0.0 +\f2\fs22 , it may skip calling +\f3\fs18 fitnessEffect() +\f2\fs22 callbacks for that individual (since the callback would not be able to change the fitness value of the individual \'96 zero multiplied by anything is still zero). It is therefore not a good idea to rely upon +\f3\fs18 fitnessEffect() +\f2\fs22 callbacks always being called, or to rely upon side effects caused by a +\f3\fs18 fitnessEffect() +\f2\fs22 callback.\ Beginning in SLiM 3.0, it is also possible to set the \f3\fs18 fitnessScaling \f2\fs22 property on a subpopulation to scale the fitness values of every individual in the subpopulation by the same constant amount, or to set the diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index 42ae3ad4..2dcb216c 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -6810,7 +6810,7 @@ In WF models this property is \f4\fs20 callbacks might also necessitate the disabling of substitution for a given mutation type; this is an important consideration to keep in mind.\ In contrast, for nonWF models this property is \f3\fs18 F -\f4\fs20 by default, because even mutations with no epistatis or other indirect fitness effects will continue to influence the survival probabilities of individuals. For nonWF models, only neutral mutation types with no epistasis or other side effects can safely be converted to substitutions upon fixation. When such a pure-neutral mutation type is defined in a nonWF model, this property should be set to +\f4\fs20 by default, because even mutations with no epistatis or other indirect fitness effects will continue to influence the survival probabilities of individuals. For nonWF models, only neutral mutation types with no epistasis or other side effects can safely be converted to substitutions upon fixation. When such a completely neutral mutation type is defined in a nonWF model, this property should be set to \f3\fs18 T \f4\fs20 to tell SLiM that substitution is allowed; this may have very large positive effects on performance, so it is important to remember when modeling background neutral mutations.\ SLiM consults this flag at the end of each tick when deciding whether to substitute each fixed mutation. If this flag is @@ -15027,12 +15027,10 @@ Note that this method is only for use in nonWF models, in which migration is man \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 \f4\fs20 \cf2 The baseline offset for the trait. This value is combined \'96 multiplicatively for multiplicative traits, additively for additive and logistic traits \'96 with all other effects that influence the trait. This provides the baseline trait value, from which individuals will deviate due to mutations and other effects on the trait that they possess.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 This property is read-write, and so can be changed. Doing so usually doesn\'92t make sense, however, because a change to the baseline offset changes the phenotype of all individuals \'96 not only future offspring, but also individuals that are already alive. Most of the time, changing the mean of the individual offset distribution for the trait, with the +This property is read-write, and so can be changed. Doing so usually doesn\'92t make sense, however, because a change to the baseline offset changes the phenotype of all individuals \'96 not only future offspring, but also individuals that are already alive. Most of the time, changing the mean of the individual offset distribution for the trait, with the \f3\fs18 individualOffsetMean \f4\fs20 property, makes more biological sense since it only affects the trait values of future offspring, leaving the phenotypes of existing individuals unchanged.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 Note that for multiplicative traits, all effects are clamped to a minimum of +Note that for multiplicative traits, all effects are clamped to a minimum of \f3\fs18 0.0 \f4\fs20 as documented in the \f3\fs18 Trait diff --git a/SLiMgui/SLiMHelpFunctions.rtf b/SLiMgui/SLiMHelpFunctions.rtf index 97043612..1d281533 100644 --- a/SLiMgui/SLiMHelpFunctions.rtf +++ b/SLiMgui/SLiMHelpFunctions.rtf @@ -1574,7 +1574,13 @@ The \f1\fs18 individualOffsetSD \f2\fs20 parameters together define a normal distribution from which individual offsets are drawn to provide what is often called \'93environmental variance\'94 or \'93developmental noise\'94. For additive traits, these drawn values are used directly as individual offsets; for multiplicative traits, they are transformed with \f1\fs18 exp() -\f2\fs20 before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale); and for logistic traits, they are used directly as individual offsets for the underlying additive trait. The default values for these parameters provide a zero-width individual offset distribution that produces no effect. Note that individual offsets can be set on individuals with +\f2\fs20 before use (or alternatively, one could instead say that the values are drawn from a lognormal distribution with the given mean and standard deviation specified on the log scale); and for logistic traits, they are used directly as individual offsets for the underlying additive trait. The default values for these parameters provide a zero-width individual offset distribution that produces no effect; for additive and logistic traits, the resulting effect is +\f1\fs18 0.0 +\f2\fs20 , whereas for multiplicative traits it is +\f1\fs18 1.0 +\f2\fs20 due to the +\f1\fs18 exp() +\f2\fs20 transform. Note that individual offsets can be set on individuals with \f1\fs18 setOffsetForTrait() \f2\fs20 or the \f6\i\fs18 @@ -1595,8 +1601,7 @@ The \f2\fs20 in quantitative-genetics models where the sum of all mutation effects is a trait value that is then translated into a fitness effect through a fitness function. It would also be \f1\fs18 F \f2\fs20 for any trait that affects an aspect of the individual other than fitness \'96 dispersal distance, for example, or aggression.\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\cf2 Finally, the +Finally, the \f1\fs18 baselineAccumulation \f2\fs20 parameter specifies the behavior of the trait when fixed mutations are turned into \f1\fs18 Substitution diff --git a/core/genomic_element_type.cpp b/core/genomic_element_type.cpp index 32fd7ce7..344afa5c 100644 --- a/core/genomic_element_type.cpp +++ b/core/genomic_element_type.cpp @@ -413,7 +413,9 @@ EidosValue_SP GenomicElementType::ExecuteMethod_setMutationFractions(EidosGlobal // check whether the mutation type is non-neutral; if so, the species is now considered non-neutral // (because we expect that a non-neutral mutation will be generated by this genomic element type) // see also Species::ExecuteContextFunction_initializeGenomicElementType() for the same logic - if (!mutation_type_ptr->all_neutral_DES_) + if (mutation_type_ptr->all_neutral_DES_) + species_.species_no_neutral_mutations_ = false; + else // !mutation_type_ptr->all_neutral_DES_ species_.species_all_neutral_mutations_ = false; } } diff --git a/core/individual.cpp b/core/individual.cpp index 28ad5b5f..296ad74a 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -2871,6 +2871,7 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be finite." << EidosTerminate(); trait_info_[trait->Index()].offset_ = new_offset; + trait->IndividualOffsetChanged(); return; } } @@ -3416,6 +3417,8 @@ void Individual::SetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_prop value->trait_info_[trait_index].offset_ = new_offset; } } + + trait->IndividualOffsetChanged(); } else { @@ -3439,6 +3442,7 @@ void Individual::SetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_prop slim_trait_index_t trait_index = trait->Index(); value->trait_info_[trait_index].offset_ = new_offset; + trait->IndividualOffsetChanged(); } } else @@ -3459,6 +3463,7 @@ void Individual::SetProperty_Accelerated_TRAIT_OFFSET(EidosGlobalStringID p_prop EIDOS_TERMINATION << "ERROR (Individual::SetProperty): property " << property_string << " is required to be a finite value (not INF or NAN)." << EidosTerminate(); value->trait_info_[trait_index].offset_ = new_offset; + trait->IndividualOffsetChanged(); } } } @@ -4687,6 +4692,8 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin ind->trait_info_[trait_index].offset_ = trait->DrawIndividualOffset(); } } + + // trait->IndividualOffsetChanged(); // the new value is drawn from the offset distribution, so we don't set this flag } } else if (offset_count == 1) @@ -4708,6 +4715,8 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin for (int individual_index = 0; individual_index < individuals_count; ++individual_index) individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset_for_trait; + + trait->IndividualOffsetChanged(); } } else if (offset_count == trait_count) @@ -4733,6 +4742,8 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin ind->trait_info_[trait_index].offset_ = offset; } + + trait->IndividualOffsetChanged(); } } else if (offset_count == trait_count * individuals_count) @@ -4772,6 +4783,8 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; } } + + trait->IndividualOffsetChanged(); } else { @@ -4789,6 +4802,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin offset = (slim_trait_offset_t)0.0; ind->trait_info_[trait_index].offset_ = offset; + trait->IndividualOffsetChanged(); } } } @@ -4832,6 +4846,8 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin individuals_buffer[individual_index]->trait_info_[trait_index].offset_ = offset; } } + + trait->IndividualOffsetChanged(); } else { @@ -4852,6 +4868,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_setOffsetForTrait(EidosGlobalStrin offset = (slim_trait_offset_t)0.0; ind->trait_info_[trait_index].offset_ = offset; + trait->IndividualOffsetChanged(); } } } @@ -6672,7 +6689,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_demandPhenotypeForIndividuals(Eido } template -void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices) +void Individual_Class::_HandleDemandForPureNeutralTraits(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &demanded_trait_indices) { // Given a vector of trait indices, this scans through them looking for "pure neutral" traits -- traits that // are known to be neutral even considering the effect of mutationEffect() callbacks, and for which there are @@ -6686,11 +6703,11 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv // seems architecturally complex, and the savings would probably be small since there are only two reads and // one write per individual here; the amount of work that could be avoided doesn't really seem large. - slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); + slim_trait_index_t demanded_trait_indices_count = (slim_trait_index_t)demanded_trait_indices.size(); - for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + for (int demanded_trait_indices_index = 0; demanded_trait_indices_index < demanded_trait_indices_count; demanded_trait_indices_index++) { - slim_trait_index_t trait_index = trait_indices[trait_indices_index]; + slim_trait_index_t trait_index = demanded_trait_indices[demanded_trait_indices_index]; Trait *trait = species->Traits()[trait_index]; if (trait->is_pure_neutral_now_) @@ -6741,13 +6758,16 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv } } - // remove the element at index trait_indices_index, decrement the count, and do this index again - trait_indices.erase(trait_indices.begin() + trait_indices_index); - trait_indices_count--; - trait_indices_index--; + // Remove the element at the current index, decrement the count, and do this index again. + // NOTE: this modifies the vector of demanded trait indices supplied by the caller; this is by design! + // It would not be correct to remove this pure-neutral trait from fitness calculations, since its + // baseline and individual offsets might still be relevant, but we can remove it from demand. + demanded_trait_indices.erase(demanded_trait_indices.begin() + demanded_trait_indices_index); + demanded_trait_indices_count--; + demanded_trait_indices_index--; #if DEBUG_TRAIT_DEMAND() - std::cout << "# " << species->community_.Tick() << " --- _HandleAndRemovePureNeutralTraits() resolved demand for trait '" << trait->Name() << "' because it is known to be neutral" << std::endl; + std::cout << "# " << species->community_.Tick() << " --- _HandleDemandForPureNeutralTraits() resolved demand for trait '" << trait->Name() << "' because it is known to be pure-neutral" << std::endl; #endif } } @@ -6755,7 +6775,7 @@ void Individual_Class::_HandleAndRemovePureNeutralTraits(Species *species, Indiv // This version of DemandPhenotype is called for a vector of individuals. This is called by the Individual method demandPhenotypeForIndividuals(). template -void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices, std::vector &mutationEffect_callbacks) +void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, const std::vector &p_trait_indices, const std::vector &mutationEffect_callbacks) { // Given a vector of individuals that are all guaranteed to belong to the provided species, and a vector of // trait indices guaranteed to be of length 1 or longer, this method loops over the chromosomes of the @@ -6812,9 +6832,13 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual // involving any non-neutral callbacks that need to be called for side effects -- then we want to handle // the trait up front here and remove it from the vector of trait indices, for efficiency downstream. // A "pure neutral" trait has phenotypes determined purely by baseline offset and individual offset. - _HandleAndRemovePureNeutralTraits(species, individuals_buffer, individuals_count, trait_indices); + // Because this modifies the vector of trait indices, we make a private copy of it here; we do not want + // to modify the caller's vector of trait indices, because handled traits are still relevant for fitness. + std::vector trait_indices = p_trait_indices; - slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); // do this after _HandleAndRemovePureNeutralTraits()! + _HandleDemandForPureNeutralTraits(species, individuals_buffer, individuals_count, trait_indices); + + slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); // do this after _HandleDemandForPureNeutralTraits()! // Next we cache method pointers for haploid and diploid chromosomes, which we will use throughout. These // are templated for efficiency, so we have to choose the correct template. That depends on the subpopulation @@ -7291,14 +7315,14 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual #endif } -template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *, Individual **, int, std::vector &, std::vector &); -template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *, Individual **, int, std::vector &, std::vector &); +template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *, Individual **, int, const std::vector &, const std::vector &); +template void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *, Individual **, int, const std::vector &, const std::vector &); // This version of DemandPhenotype is called for a whole subpopulation. This allows for greater efficiency than the individual-level version of this method. // This is called by the Subpopulation method demandPhenotype(), and by SLiM's internal fitness calculation code for traits with a direct effect on fitness. template -void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector &p_subpop_mutationEffect_callbacks) +void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, const std::vector &p_trait_indices, const std::vector &p_subpop_mutationEffect_callbacks) { // FIXME MULTITRAIT: think about shuffling the order in which individuals are handled, in this code path @@ -7322,9 +7346,13 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s // involving any non-neutral callbacks that need to be called for side effects -- then we want to handle // the trait up front here and remove it from the vector of trait indices, for efficiency downstream. // A "pure neutral" trait has phenotypes determined purely by baseline offset and individual offset. - _HandleAndRemovePureNeutralTraits(species, individuals_buffer, individuals_count, trait_indices); + // Because this modifies the vector of trait indices, we make a private copy of it here; we do not want + // to modify the caller's vector of trait indices, because handled traits are still relevant for fitness. + std::vector trait_indices = p_trait_indices; + + _HandleDemandForPureNeutralTraits(species, individuals_buffer, individuals_count, trait_indices); - slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); // do this after _HandleAndRemovePureNeutralTraits()! + slim_trait_index_t trait_indices_count = (slim_trait_index_t)trait_indices.size(); // do this after _HandleDemandForPureNeutralTraits()! // For a given individual, for a given trait, we have to make a decision as to whether we will recalculate or not. That decision gets made // once and then holds across all chromosomes for the individual. But we're looping over chromosomes at the topmost level, so we have a @@ -7701,8 +7729,8 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s #endif } -template void Individual_Class::DemandPhenotype_SUBPOP(Species *, Subpopulation *, std::vector &, std::vector &); -template void Individual_Class::DemandPhenotype_SUBPOP(Species *, Subpopulation *, std::vector &, std::vector &); +template void Individual_Class::DemandPhenotype_SUBPOP(Species *, Subpopulation *, const std::vector &, const std::vector &); +template void Individual_Class::DemandPhenotype_SUBPOP(Species *, Subpopulation *, const std::vector &, const std::vector &); // Low-level method to calculate a phenotype for one individual, for one haploid (or hemizygous) chromosome, diff --git a/core/individual.h b/core/individual.h index 03793653..bb7c28fc 100644 --- a/core/individual.h +++ b/core/individual.h @@ -462,17 +462,23 @@ class Individual_Class : public EidosDictionaryUnretained_Class EidosValue_SP ExecuteMethod_demandPhenotypeForIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const; + // As the name suggests, this detects "pure-neutral" traits where the combination of the genetics of the + // trait and callbacks affecting the trait together guarantee that all genetic effects on the trait are + // neutral and no callbacks actually need to be called; and in such cases, it handles the trait itself and + // removes it from the vector of traits experiencing demand. Note that this still needs to factor in the + // baseline and individual offsets for the trait, which can be non-neutral even in the "pure-neutral" case. + // BEWARE: this can modify the vector demanded_trait_indices that is passed in to it! template - static void _HandleAndRemovePureNeutralTraits(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices); + static void _HandleDemandForPureNeutralTraits(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &demanded_trait_indices); // phenotype demand for all traits for a vector of target individuals, across all chromosomes // if f_force_recalc is true all values are recalculated; if false, only NAN trait values are recalculated // see also the methods class _IncorporateEffects_X() methods in class Individual, called by this method template - static void DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, std::vector &trait_indices, std::vector &mutationEffect_callbacks); + static void DemandPhenotype_INDIVIDUALS(Species *species, Individual **individuals_buffer, int individuals_count, const std::vector &p_trait_indices, const std::vector &mutationEffect_callbacks); template - static void DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, std::vector &trait_indices, std::vector &p_subpop_mutationEffect_callbacks); + static void DemandPhenotype_SUBPOP(Species *species, Subpopulation *subpop, const std::vector &p_trait_indices, const std::vector &p_subpop_mutationEffect_callbacks); }; diff --git a/core/mutation.cpp b/core/mutation.cpp index 9fd457eb..438b15fa 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -184,8 +184,8 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ if (mutation_type_ptr_->all_neutral_DES_) { - // The DES of the mutation type is pure neutral, so we don't need to do any draws; we can short-circuit - // most of the work here and just set up neutral effects for all of the traits. + // The DES of the mutation type is neutral, so we don't need to do any draws; we can + // skip most of the work here and just set up neutral effects for all of the traits. for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; @@ -212,9 +212,10 @@ mutation_type_ptr_(p_mutation_type_ptr), position_(p_position), subpop_index_(p_ } else { - // The DES of the mutation type is not pure neutral. Note that species.species_all_neutral_mutations_ might still be true - // at this point; the mutation type for this mutation might not be used by any genomic element type, - // because we might be getting called by addNewDrawnMutation() for a type that is otherwise unused. + // The DES of the mutation type is not neutral. Note that species.species_all_neutral_mutations_ + // might still be true at this point; the mutation type for this mutation might not be used by any + // genomic element type, because we might be getting called by addNewDrawnMutation() for a type that + // is otherwise unused. for (slim_trait_index_t trait_index = 0; trait_index < trait_count; ++trait_index) { MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; diff --git a/core/mutation_run.cpp b/core/mutation_run.cpp index 78b717ba..f571e1f0 100644 --- a/core/mutation_run.cpp +++ b/core/mutation_run.cpp @@ -33,11 +33,11 @@ std::ostream& operator<<(std::ostream& p_out, TraitCalculationRegime p_trait_typ { switch (p_trait_type) { - case TraitCalculationRegime::kUndefined: p_out << "kUndefined"; break; - case TraitCalculationRegime::kPureNeutral: p_out << "kPureNeutral"; break; - case TraitCalculationRegime::kNoActiveCallbacks: p_out << "kNoActiveCallbacks"; break; - case TraitCalculationRegime::kAllNeutralCallbacks: p_out << "kAllNeutralCallbacks"; break; - case TraitCalculationRegime::kNonNeutralCallbacks: p_out << "kNonNeutralCallbacks"; break; + case TraitCalculationRegime::kUndefined: p_out << "kUndefined"; break; + case TraitCalculationRegime::kPureNeutral: p_out << "kPureNeutral"; break; + case TraitCalculationRegime::kNoActiveCallbacks: p_out << "kNoActiveCallbacks"; break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: p_out << "kAllGlobalNeutralCallbacks"; break; + case TraitCalculationRegime::kNonNeutralCallbacks: p_out << "kNonNeutralCallbacks"; break; } return p_out; @@ -469,10 +469,20 @@ void MutationRun::split_run(Mutation *p_mut_block_ptr, MutationRun **p_first_hal void MutationRun::cache_nonneutral_mutations_REGIME_0(IndDomCacheIndex inddom_cache_count) const { // - // Regime 0 means there are no genetic effects at all, so we can simply empty the non-neutral cache. - // FIXME MULTITRAIT: we want to avoid allocating the nonneutral cache at all, here, if it is not allocated yet + // Regime 0 means there are no genetic effects at all, even considering callbacks, so we can simply empty + // the non-neutral cache; this is often called "pure-neutral" in SLiM's code. Note that this means that + // the genetics (including callbacks) are neutral for the trait; it does NOT mean that the trait is neutral + // for the individual, particularly since baseline and individual offsets are still included in the trait + // calculations. In this regime, if the nonneutral cache is unallocated, we leave it unallocated; there is + // no need for it. If it is allocated, we empty it but leave it allocated; this state means that the model + // has been non-pure-neutral in the past, and so it might return to being non-pure-neutral in the future, + // so we want to avoid alloc/free thrash. This will not always be the right choice; we could decrease our + // memory footprint by freeing the buffers here, which would be a win if we stay pure-neutral for a long + // time. But I think having somewhat higher memory usage is preferable to the possibility of massive alloc + // thrash in models that, for example, have mostly empty genomes with occasional sweeps -- perhaps a common + // mode for tree-sequence recording models! // - zero_out_nonneutral_cache(inddom_cache_count); + zero_out_nonneutral_cache_NOALLOC(); } void MutationRun::cache_nonneutral_mutations_REGIME_1(Mutation *p_mut_block_ptr, IndDomCacheIndex inddom_cache_count) const @@ -561,9 +571,9 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, // Regime 3 means that there are active mutationEffect() callbacks beyond the constant neutral global // callbacks of regime 2 -- but those non-global-neutral callbacks might not affect the mutation. So // we first consult subject_to_mutationEffect_callback_ to find out if a callback of any kind affects - // the mutation. If that is true, then we consult subject_to_non_neutral_callback_; if that is true, - // the mutation is affected by a nonneutral callback, which must be called even if it is overridden - // by a global-neutral callback since it might have side effects. If subject_to_non_neutral_callback_ + // the mutation. If that is true, then we consult subject_to_non_global_neutral_callback_; if that is + // true, the mutation is affected by a nonneutral callback, which must be called even if it is overridden + // by a global-neutral callback (it might have side effects). If subject_to_non_global_neutral_callback_ // is false, we know the mutation is rendered neutral by a global-neutral callback. And if the test // of subject_to_mutationEffect_callback_ was false, the mutation's neutral flag is reliable. // @@ -583,7 +593,7 @@ void MutationRun::cache_nonneutral_mutations_REGIME_3(Mutation *p_mut_block_ptr, if (muttypeptr->subject_to_mutationEffect_callback_) { - if (muttypeptr->subject_to_non_neutral_callback_) + if (muttypeptr->subject_to_non_global_neutral_callback_) { if (buffer_count == buffer_capacity) { diff --git a/core/mutation_run.h b/core/mutation_run.h index 5ac5427f..23d64d28 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -121,11 +121,11 @@ typedef struct MutationRunContext { // callbacks present, whether independent dominance is being used, and other factors. This affects the way // that non-neutral caches for MutationRun are constructed, and can be used in other ways as well. enum class TraitCalculationRegime : int8_t { - kUndefined = -1, // used for the initial state - kPureNeutral = 0, // all mutations are effectively neutral; genetics can be skipped - kNoActiveCallbacks, // no active callbacks, so you don't have to look at the mutation type at all - kAllNeutralCallbacks, // we can skip actually calling all callbacks, since they are all global-neutral - kNonNeutralCallbacks, // we can't skip calling all callbacks, since some are non-global-neutral + kUndefined = -1, // used for the initial state + kPureNeutral = 0, // all mutations are effectively neutral, including callbacks; genetics can be skipped, but offsets still matter + kNoActiveCallbacks, // no active callbacks, so you don't have to look at the mutation type at all + kAllGlobalNeutralCallbacks, // we can skip actually calling all callbacks, since they are all global-neutral + kNonNeutralCallbacks, // we can't skip calling all callbacks, since some are non-global-neutral }; std::ostream& operator<<(std::ostream& p_out, TraitCalculationRegime p_trait_type); @@ -321,28 +321,6 @@ class MutationRun // for independent dominance are not used; instead the effects are calculated from the non-neutral mutation // buffer directly, to get the correct hemizygous effects. FIXME MULTITRAIT: Maybe we can be smarter here... - // BCH 1/19/2025: PLANNED CHANGES: - // - // - I need to figure out what it means for nonneutral_cache_ to be nullptr. There are three useful meanings - // for this that I see: (a) the non-neutral cache is simply uninitialized/invalid, and will be allocated - // when it is time to set it up; (b) the non-neutral cache is in a valid state representing the fact that - // there are no nonneutral mutations; the cache is simply empty, equivalent to count_ == 0; or (c) the - // non-neutral cache is in a valid state representing the fact that ALL mutations are nonneutral, and so - // the non-neutral cache would contain identical data to the main mutation buffer for the mutation run. - // Independent dominance interacts with this; for (c) we would probably really want to have an allocated - // NonNeutralCache struct, but with zero capacity and zero mutations, with a special flag set somewhere - // so we know that this means state (c). With the above planned change we should have space to add such - // a flag, or we could use a special capacity value of -1 or something if we need to wedge it in. For - // (a) and (b) maybe both can share the state. Prior to non-neutral cache validation, a nullptr value - // can represent either state. After non-neutral cache validation, all caches have been validated and - // so state (a) will no longer exist; we will allocate a NonNeutralCache struct for every mutation run - // UNLESS we determine that we are in state (b), in which case we leave it unallocated to represent (b). - // This determination will be made again each time we validate; if the model stays neutral the pointer - // will remain nullptr. If, at any point, the validation process determines non-neutrality allocation - // will occur. If the model goes back to being neutral, deallocation will not be done; the allocated - // state is "sticky" to avoid allocation thrash, since we don't expect a non-neutral model to revert to - // neutrality permanently. - // This struct represents the entire non-neutral cache, which can't entirely be described with a C struct due // to variable-length elements. First are the capacity and count for the nonneutral mutation buffer. Then // come slim_effect_t entries, one per trait in the species for which we have an independent-dominance cache @@ -359,7 +337,47 @@ class MutationRun // the non-neutral MutationIndex buffer begins after the last entry in independent_dominance_cache_ } NonNeutralCache; - mutable NonNeutralCache *nonneutral_cache_ = nullptr; // OWNED POINTER: the contents of the nonneutal buffer, or nullptr + // The nonneutral_cache_ pointer below can be nullptr for any of three reasons: + // + // (a) The non-neutral cache is simply uninitialized/invalid, and will be allocated when it is time to set + // it up. This is the reason if none of the below reasons is applicable. This state is equivalent to + // being allocated and having a nonneutral_count_ == -1. + // + // (b) The non-neutral cache is in a valid state representing the fact that there are no nonneutral mutations; + // the cache is simply empty, equivalent to being allocated and having nonneutral_count_ == 0. This + // occurs when a model has been "pure-neutral" since the beginning (or since this mutation run was + // created); it is managed by cache_nonneutral_mutations_REGIME_0(), which handles the trait calculation + // regime for pure-neutral models. The marker for being in this state is being in regime kPureNeutral, + // but in general no special checks for nullptr should be needed here; if the species is pure-neutral, + // nobody should be looking at the non-neutral cache anyway. + // + // (c) The non-neutral cache is in a valid state representing the fact that ALL mutations are nonneutral, + // AND no traits are candidates for independent dominance. In this case, there is no need for the + // nonneutral cache to be allocated; we will just use our main mutation buffer instead. The marker + // for being in this state is the species_no_neutral_mutations_ flag of Species AND the count of // FIXME MULTITRAIT no, the marker should be being in a specific regime + // independent-dominance cache slots provided by IndependentDominanceCacheCount() being zero, but in + // many places in the code this is the ONLY reason that nonneutral_cache_ will be nullptr legally; in + // those places, these preconditions will only be checked in DEBUG. + // + // In addition to the above, there is one weird case where the nonneutral_cache_ pointer is allocated but + // is in an unusual state. This occurs when ALL mutations are nonneutral, but case (c) above is not true + // because at least one trait is a candidate for independent dominance (i.e., has an inddom cache slot). + // In this case we do not want to set up the non-neutral mutation buffer, because it would be identical to + // our main mutation buffer, but we do want to allocate the non-neutral cache because we need a place to + // put our inddom caches. In this special state, nonneutral_count_ is set to the same count as the main + // buffer, but nonneutral_capacity_ is set to zero. This would normally be a nonsensical state, so we use + // it as a special marker to indicate that the main mutation buffer should be used. // FIXME MULTITRAIT no, the marker should be being in a specific regime + // + // Note that the nonneutral cache is validated during trait calculation, and should remain valid thereafter + // unless marked as invalid by having nonneutral_count_ set to -1. Since mutation runs are usually immutable, + // their caches tend to stay valid. However, if a mutation run does get modified that invalidates its + // nonneutral cache; and if the state of a mutation itself changes in a way that would necessitate a recache, + // that should be handled by the chromosome and the species. + // FIXME MULTITRAIT: this invalidation mechanism needs to be done/checked. + // FIXME MULTITRAIT: Also, there need to be checks in place that demonstrate that nonneutral caches are not + // re-made unnecessarily, but are instead carried over except when they get invalidated. + // + mutable NonNeutralCache *nonneutral_cache_ = nullptr; // OWNED POINTER: the contents of the nonneutal cache #endif // SLIM_USE_NONNEUTRAL_CACHES() @@ -856,6 +874,14 @@ class MutationRun nonneutral_cache_->nonneutral_count_ = 0; } + inline __attribute__((always_inline)) void zero_out_nonneutral_cache_NOALLOC(void) const + { + // This variant of zero_out_nonneutral_cache() empties an existing buffer, but does not allocate a new + // buffer if one does not already exist. See cache_nonneutral_mutations_REGIME_0() for comments. + if (nonneutral_cache_) + nonneutral_cache_->nonneutral_count_ = 0; + } + inline __attribute__((always_inline)) void expand_nonneutral_buffer(IndDomCacheIndex inddom_cache_count) const { #ifdef __clang_analyzer__ @@ -933,6 +959,11 @@ class MutationRun // Memory usage tallying, for outputUsage() size_t MemoryUsageForMutationIndexBuffers(void) const; size_t MemoryUsageForNonneutralCaches(slim_trait_index_t trait_count) const; + +#if DEBUG + // Friends who are allowed to poke around inside MutationRun, but only in DEBUG builds + friend class Species; +#endif }; // We need MutationType below, but we can't include it at top because it requires MutationRun to be defined... diff --git a/core/mutation_type.cpp b/core/mutation_type.cpp index 54696a75..1d1ad02f 100644 --- a/core/mutation_type.cpp +++ b/core/mutation_type.cpp @@ -84,8 +84,8 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr // intentionally no bounds check for p_default_dominance // determine whether this mutation type has a neutral DES - // note that we do not set species_all_neutral_mutations_ = false here; we wait until this muttype is used - // see Species::ExecuteContextFunction_initializeGenomicElementType() and similar spots for that logic + // note that we do not change species_all_neutral_mutations_ or species_no_neutral_mutations_ here; we wait + // until this muttype is used (see Species::ExecuteContextFunction_initializeGenomicElementType() etc.) all_neutral_DES_ = ((p_DES_type == DESType::kFixed) && (p_DES_parameters[0] == 0.0)); // initially, whether a mutation type has any neutral mutations is inherited from whether it has a neutral DES diff --git a/core/mutation_type.h b/core/mutation_type.h index b024fcab..cdb6b8a1 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -197,19 +197,25 @@ class MutationType : public EidosDictionaryUnretained // because muttype_all_neutral_mutations_ is set and the mutation type cannot be influenced by any callbacks // in the current subpopulation / tick, or because an active callback actually sets this mutation type to be // neutral in this subpopulation / tick. Mutation types for which this flag is set can be safely elided from - // trait calculations altogether. If this flag is set for all muttypes, chromosome-based trait calculations - // will be skipped altogether for this tick. + // trait calculations altogether (although the baseline and individual offsets for the trait still matter). + // If this flag is set for all muttypes, genetics-based trait calculations will be skipped altogether. mutable bool is_pure_neutral_now_; mutable bool previous_is_pure_neutral_now_; // If set, subject_to_mutationEffect_callback_ indicates that the muttype is influenced by a mutationEffect - // callback in at least one subpop. Mutation types with this flag set are subject to a callback, and so the - // effect of mutations of that type cannot be consulted reliably; the callback needs to be considered. + // callback in at least one subpop. The effect of mutations of this type cannot be consulted reliably; the + // callback needs to be considered. But this does not necessarily means that the callback actually needs to + // be called, since it might be global-neutral. We avoid setting this flag at all if the callback can be + // ignored: if (1) it sets neutrality, and the mutation type is known to already be neutral; or (2) it is + // specific to a subpopulation or mutation type that does not currently exist in the simulation. mutable bool subject_to_mutationEffect_callback_; mutable bool previous_subject_to_mutationEffect_callback_; - mutable bool subject_to_non_neutral_callback_; - mutable bool previous_subject_to_non_neutral_callback_; + // If set, subject_to_non_global_neutral_callback_ indicates that the muttype is influenced by a callback + // in at least one subpop that is something other than a global-neutral callback, and thus needs to be + // called because it potentially changes values for mutations of this type in a way that can't be predicted. + mutable bool subject_to_non_global_neutral_callback_; + mutable bool previous_subject_to_non_global_neutral_callback_; #ifdef SLIMGUI diff --git a/core/population.cpp b/core/population.cpp index 68388d95..903d1f50 100644 --- a/core/population.cpp +++ b/core/population.cpp @@ -5305,12 +5305,12 @@ void Population::RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recal species_.PrepareForTraitCalculations(mutationEffect_callbacks); // we need to recalculate phenotypes for traits that have a direct effect on fitness - std::vector p_direct_effect_trait_indices; + std::vector direct_effect_trait_indices; const std::vector &traits = species_.Traits(); for (slim_trait_index_t trait_index = 0; trait_index < species_.TraitCount(); ++trait_index) if (traits[trait_index]->HasDirectFitnessEffect()) - p_direct_effect_trait_indices.push_back(trait_index); + direct_effect_trait_indices.push_back(trait_index); SLiMEidosBlockType old_executing_block_type = community_.executing_block_type_; community_.executing_block_type_ = SLiMEidosBlockType::SLiMEidosMutationEffectCallback; // used for both mutationEffect() and fitnessEffect() for simplicity @@ -5325,7 +5325,7 @@ void Population::RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recal std::vector no_callbacks; for (std::pair &subpop_pair : subpops_) - subpop_pair.second->UpdateFitness(no_callbacks, no_callbacks, p_direct_effect_trait_indices, p_force_trait_recalculation); + subpop_pair.second->UpdateFitness(no_callbacks, no_callbacks, direct_effect_trait_indices, p_force_trait_recalculation); } else { @@ -5353,7 +5353,7 @@ void Population::RecalculateFitness(slim_tick_t p_tick, bool p_force_trait_recal } // Update fitness values, using the callbacks - subpop->UpdateFitness(subpop_mutationEffect_callbacks, subpop_fitnessEffect_callbacks, p_direct_effect_trait_indices, p_force_trait_recalculation); + subpop->UpdateFitness(subpop_mutationEffect_callbacks, subpop_fitnessEffect_callbacks, direct_effect_trait_indices, p_force_trait_recalculation); } } diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index b89ae4af..bdae2e0a 100644 --- a/core/slim_globals.cpp +++ b/core/slim_globals.cpp @@ -1455,6 +1455,11 @@ const std::string &gStr_treeSeqSimplify = EidosRegisteredString("treeSeqSimplify const std::string &gStr_treeSeqRememberIndividuals = EidosRegisteredString("treeSeqRememberIndividuals", gID_treeSeqRememberIndividuals); const std::string &gStr_treeSeqOutput = EidosRegisteredString("treeSeqOutput", gID_treeSeqOutput); const std::string &gStr__debug = EidosRegisteredString("_debug", gID__debug); +const std::string &gStr__debugBuild = EidosRegisteredString("_debugBuild", gID__debugBuild); +#if DEBUG +const std::string &gStr__allocatedNonneutralCacheCount = EidosRegisteredString("_allocatedNonneutralCacheCount", gID__allocatedNonneutralCacheCount); +const std::string &gStr__traitCalculationRegimeName = EidosRegisteredString("_traitCalculationRegimeName", gID__traitCalculationRegimeName); +#endif const std::string &gStr_setMigrationRates = EidosRegisteredString("setMigrationRates", gID_setMigrationRates); const std::string &gStr_deviatePositions = EidosRegisteredString("deviatePositions", gID_deviatePositions); const std::string &gStr_deviatePositionsWithMap = EidosRegisteredString("deviatePositionsWithMap", gID_deviatePositionsWithMap); diff --git a/core/slim_globals.h b/core/slim_globals.h index aa28c598..b55c18cb 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -1065,7 +1065,12 @@ extern const std::string &gStr_treeSeqCoalesced; extern const std::string &gStr_treeSeqSimplify; extern const std::string &gStr_treeSeqRememberIndividuals; extern const std::string &gStr_treeSeqOutput; -extern const std::string &gStr__debug; // internal +extern const std::string &gStr__debug; // internal API +extern const std::string &gStr__debugBuild; // internal API +#if DEBUG +extern const std::string &gStr__allocatedNonneutralCacheCount; // internal API +extern const std::string &gStr__traitCalculationRegimeName; // internal API +#endif extern const std::string &gStr_setMigrationRates; extern const std::string &gStr_deviatePositions; extern const std::string &gStr_deviatePositionsWithMap; @@ -1562,7 +1567,12 @@ enum _SLiMGlobalStringID : int { gID_treeSeqSimplify, gID_treeSeqRememberIndividuals, gID_treeSeqOutput, - gID__debug, // internal + gID__debug, // internal API + gID__debugBuild, // internal API +#if DEBUG + gID__allocatedNonneutralCacheCount, // internal API + gID__traitCalculationRegimeName, // internal API +#endif gID_setMigrationRates, gID_deviatePositions, gID_deviatePositionsWithMap, diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index cbdde40f..831c7df2 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1688,14 +1688,259 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptSuccess(test_zygosity4); + // ========================================================================================================= + // + // Below is a set of test models that will (hopefully) exercise all the different code paths for trait + // evaluation -- callbacks, different DES combinations, neutral and non-neutral, etc. These models are + // intended both to test themselves to some extent, especially in DEBUG builds using internal properties + // for diagnostics, and also to be tested by _CheckPhenotypeForTrait()'s cross-checks in DEBUG builds. + // + // ========================================================================================================= + + // a completely neutral model using the default trait (no initializeTrait() call) + // - fitness values should be 1.0 for all individuals + // - the trait should be "super-pure-neutral" and thus skipped and uncalculated (and thus NAN) + // - this model should not allocate any non-neutral caches, and should be in the kPureNeutral regime + std::string multitrait_DEFAULT_NEUTRAL = + R"V0G0N( +// multitrait_DEFAULT_NEUTRAL +initialize() { + initializeMutationType("m1", 0.5, "f", 0.0); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 999999); + initializeMutationRate(1e-7); + initializeRecombinationRate(1e-8); +} +1 early() { sim.addSubpop("p1", 50); } +2: first() { + inds = p1.individuals; + for (ind in inds) { + ind_t1 = ind.phenotypeForTrait("simT"); // dynamic property not defined for the default trait + ind_fitness = ind.cachedFitness; + + if (!isNAN(ind_t1)) + stop("t1 value mismatch (NAN expected): " + ind_t1); + if (ind_fitness != 1.0) + stop("fitness mismatch (1.0 expected): " + ind_fitness); + } + if (sim._debugBuild) { + if (sim._allocatedNonneutralCacheCount != 0) + stop("nonneutral caches were allocated despite all neutral genetics"); + if (sim._traitCalculationRegimeName != "kPureNeutral") + stop("unexpected trait calculation regime"); + } +} +100 late() { } + )V0G0N"; + + SLiMAssertScriptSuccess(multitrait_DEFAULT_NEUTRAL); + + // a completely neutral model using the default trait (no initializeTrait() call), with a subpopulation + // fitnessScaling value and several tricky mutationEffect() callbacks to obfuscate inference. + // - fitness values should be 1.0 for all individuals + // - the trait should be "super-pure-neutral" and thus skipped and uncalculated (and thus NAN) + // - this model should not allocate any non-neutral caches, and should be in the kPureNeutral regime + std::string multitrait_DEFAULT_NEUTRAL_OBFUSCATED = + R"V0G0N( +// multitrait_DEFAULT_NEUTRAL_OBFUSCATED +initialize() { + initializeMutationType("m1", 0.5, "f", 0.0); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 999999); + initializeMutationRate(1e-7); + initializeRecombinationRate(1e-8); +} +1 early() { sim.addSubpop("p1", 50); } +late() { p1.fitnessScaling = 1.25; } +mutationEffect(m1) { return NULL; } +mutationEffect(m1) { return 1.0; } +mutationEffect(m1, p1) { return 1.0; } +mutationEffect(m1, p2) { return effect; } // considered non-neutral, but subpop nonexistent +2: first() { + inds = p1.individuals; + for (ind in inds) { + ind_t1 = ind.phenotypeForTrait("simT"); // dynamic property not defined for the default trait + ind_fitness = ind.cachedFitness; + + if (!isNAN(ind_t1)) + stop("t1 value mismatch (NAN expected): " + ind_t1); + if (ind_fitness != 1.25) + stop("fitness mismatch (1.25 expected): " + ind_fitness); + } + if (sim._debugBuild) { + if (sim._allocatedNonneutralCacheCount != 0) + stop("nonneutral caches were allocated despite all neutral genetics"); + if (sim._traitCalculationRegimeName != "kPureNeutral") + stop("unexpected trait calculation regime"); + } +} +100 late() { } + )V0G0N"; - // Complex multi-trait, multi-chrom models that will (hopefully) exercise all the different code paths for - // phenotype evaluation -- callbacks, different DES combinations, neutral and non-neutral, etc. - // The intention here is that these models don't test themselves; they are stochastic and there is no - // expectation regarding their outcome. Instead, _CheckPhenotypeForTrait() cross-checks them under DEBUG. + SLiMAssertScriptSuccess(multitrait_DEFAULT_NEUTRAL_OBFUSCATED); - std::string complex_multi_1 = // this is an abbreviated version of test script complex_multi_test_1.slim - R"V0G0N( + // a quick test model for what happens if a non-neutral muttype is defined but not used in a neutral model + // the goal was to provoke a conflict between muttype and trait ideas of whether the model is neutral + std::string multitrait_DEFAULT_NEUTRAL_NNMUTTYPE = + R"V0G0N( +// multitrait_DEFAULT_NEUTRAL_NNMUTTYPE +initialize() { + initializeMutationRate(1e-7); + initializeMutationType("m1", 0.5, "f", 0.0); + initializeMutationType("m2", 0.5, "f", 0.1); // nonneutral + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElementType("g2", m2, 1.0); // nonneutral but unused + initializeGenomicElement(g1, 0, 99999); + initializeRecombinationRate(1e-8); +} +1 early() { sim.addSubpop("p1", 50); } +100 late() { } + )V0G0N"; + + SLiMAssertScriptSuccess(multitrait_DEFAULT_NEUTRAL_NNMUTTYPE); + + // a completely neutral model with constant baseline and individual offsets, with direct fitness effects + // - fitness values should be 1.0 for all individuals + // - traits should be "super-pure-neutral" and thus skipped and uncalculated (and thus NAN) + // - this model should not allocate any non-neutral caches, and should be in the kPureNeutral regime + std::string multitrait_COMPLETE_NEUTRAL = + R"V0G0N( +// multitrait_COMPLETE_NEUTRAL +initialize() { + defineConstant("t1", initializeTrait("t1", "a", 1.0, directFitnessEffect=T)); + defineConstant("t2", initializeTrait("t2", "m", directFitnessEffect=T)); + initializeMutationType("m1", 0.5, "f", 0.0); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 999999); + initializeMutationRate(1e-7); + initializeRecombinationRate(1e-8); +} +1 early() { sim.addSubpop("p1", 50); } +2: first() { + inds = p1.individuals; + for (ind in inds) { + ind_t1 = ind.t1; + ind_t2 = ind.t2; + ind_fitness = ind.cachedFitness; + + if (!isNAN(ind_t1)) + stop("t1 value mismatch (NAN expected): " + ind_t1); + if (!isNAN(ind_t2)) + stop("t2 value mismatch (NAN expected): " + ind_t2); + if (ind_fitness != 1.0) + stop("fitness mismatch (1.0 expected): " + ind_fitness); + } + if (sim._debugBuild) { + if (sim._allocatedNonneutralCacheCount != 0) + stop("nonneutral caches were allocated despite all neutral genetics"); + if (sim._traitCalculationRegimeName != "kPureNeutral") + stop("unexpected trait calculation regime"); + } +} +100 late() { } + )V0G0N"; + + SLiMAssertScriptSuccess(multitrait_COMPLETE_NEUTRAL); + + // two traits, both genetically neutral but with non-zero fitness effects + // the additive trait is NOT configured for independent dominance + // - fitness values should be predictable from baseline offsets + // - traits should be "super-pure-neutral" and thus skipped and uncalculated (and thus NAN) + // - this model should not allocate any non-neutral caches, and should be in the kPureNeutral regime + std::string multitrait_NEUTRAL_GENETICS = + R"V0G0N( +// multitrait_NEUTRAL_GENETICS +initialize() { + defineConstant("t1", initializeTrait("t1", "a", 1.01, directFitnessEffect=T)); + defineConstant("t2", initializeTrait("t2", "m", 1.01, directFitnessEffect=T)); + initializeMutationType("m1", 0.5, "f", 0.0); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 999999); + initializeMutationRate(1e-7); + initializeRecombinationRate(1e-8); +} +1 early() { sim.addSubpop("p1", 50); } +late() { p1.fitnessScaling = runif(1); defineGlobal("lastFitnessScaling", p1.fitnessScaling); } +2: first() { + inds = p1.individuals; + for (ind in inds) { + ind_fitness = ind.cachedFitness; + expected_fitness = t1.baselineOffset * t2.baselineOffset * lastFitnessScaling; + + if (!isNAN(ind.t1)) + stop("t1 value mismatch (NAN expected): " + ind.t1); + if (!isNAN(ind.t2)) + stop("t2 value mismatch (NAN expected): " + ind.t2); + if (!isClose(ind_fitness, expected_fitness)) + stop("fitness mismatch: " + ind_fitness + ", " + expected_fitness); + } + if (sim._debugBuild) { + if (sim._allocatedNonneutralCacheCount != 0) + stop("nonneutral caches were allocated despite all neutral genetics"); + if (sim._traitCalculationRegimeName != "kPureNeutral") + stop("unexpected trait calculation regime"); + } +} +100 late() { } + )V0G0N"; + + SLiMAssertScriptSuccess(multitrait_NEUTRAL_GENETICS); + + + // two traits, both genetically neutral but with non-zero fitness effects and non-uniform offsets + // the additive trait is NOT configured for independent dominance + // - fitness values should be predictable from baseline/individual offsets + // - this model should not allocate any non-neutral caches, and should be in the kPureNeutral regime + std::string multitrait_NEUTRAL_GENETICS_WITH_OFFSETS = + R"V0G0N( +// multitrait_NEUTRAL_GENETICS_WITH_OFFSETS +initialize() { + defineConstant("t1", initializeTrait("t1", "a", 1.01, 0.0, 0.01, directFitnessEffect=T)); + defineConstant("t2", initializeTrait("t2", "m", 1.01, 0.0, 0.01, directFitnessEffect=T)); + initializeMutationType("m1", 0.5, "f", 0.0); + initializeGenomicElementType("g1", m1, 1.0); + initializeGenomicElement(g1, 0, 999999); + initializeMutationRate(1e-7); + initializeRecombinationRate(1e-8); +} +1 early() { sim.addSubpop("p1", 50); } +late() { p1.fitnessScaling = runif(1); defineGlobal("lastFitnessScaling", p1.fitnessScaling); } +2: first() { + inds = p1.individuals; + for (ind in inds) { + ind_t1 = ind.t1; + ind_t2 = ind.t2; + ind_fitness = ind.cachedFitness; + expected_t1 = t1.baselineOffset + ind.t1Offset; + expected_t2 = t2.baselineOffset * ind.t2Offset; + expected_fitness = ind_t1 * ind_t2 * lastFitnessScaling; + + if (!isClose(ind_t1, expected_t1)) + stop("t1 value mismatch: " + ind_t1 + ", " + expected_t1); + if (!isClose(ind_t2, expected_t2)) + stop("t2 value mismatch: " + ind_t2 + ", " + expected_t2); + if (!isClose(ind_fitness, expected_fitness)) + stop("fitness mismatch: " + ind_fitness + ", " + expected_fitness); + } + if (sim._debugBuild) { + if (sim._allocatedNonneutralCacheCount != 0) + stop("nonneutral caches were allocated despite all neutral genetics"); + if (sim._traitCalculationRegimeName != "kPureNeutral") + stop("unexpected trait calculation regime"); + } +} +100 late() { } + )V0G0N"; + + SLiMAssertScriptSuccess(multitrait_NEUTRAL_GENETICS_WITH_OFFSETS); + + + // This is a particularly complex multitrait model intended to test many different things at once, + // including pleiotropy, independent dominance, direct and indirect effects, and so forth. + // This is an abbreviated version of test script complex_multi_test_1.slim + std::string multitrait_COMPLEX_1 = + R"V0G0N( +// multitrait_COMPLEX_1 initialize() { defineConstant("I1", 5.0); defineConstant("I2", -5.0); @@ -1762,7 +2007,7 @@ initialize() { initializeRecombinationRate(1e-8); if (id == 1) - initializeGenomicElement(g1); // autosome 1 is pure neutral, using only m1 + initializeGenomicElement(g1); // autosome 1 is pure-neutral, using only m1 else initializeGenomicElement(g2); // autosome 2 is a mix, using m1 / m2 / m3 } @@ -1832,9 +2077,9 @@ mutation(m3) { inds = sim.subpopulations.individuals; // check that traits were calculated correctly, or left uncalculated as appropriate - if (!all(inds.n1T == inds.offsetForTrait("n1T"))) stop("n1T was calculated incorrectly"); - if (!all(isNAN(inds.n2T))) stop("n2T was calculated unnecessarily"); - if (!all(isNAN(inds.n3T))) stop("n3T was calculated unnecessarily"); + if (!all(isNAN(inds.n1T))) stop("n1T was calculated unnecessarily (super-pure-neutral trait)"); + if (!all(isNAN(inds.n2T))) stop("n2T was calculated unnecessarily (no direct fitness effect)"); + if (!all(isNAN(inds.n3T))) stop("n3T was calculated unnecessarily (no direct fitness effect)"); if (!all(!isNAN(inds.logistic1T))) stop("logistic1T is NAN"); if (!all((inds.logistic1T >= 0.0) & (inds.logistic1T <= 1.0))) stop("logistic1T is out of range"); @@ -1860,9 +2105,9 @@ mutation(m3) { } 100 late() { } - )V0G0N"; + )V0G0N"; - SLiMAssertScriptSuccess(complex_multi_1); + SLiMAssertScriptSuccess(multitrait_COMPLEX_1); std::cout << "_RunMultitraitTests() done" << std::endl; } diff --git a/core/species.cpp b/core/species.cpp index dbda4127..13cf05a0 100644 --- a/core/species.cpp +++ b/core/species.cpp @@ -255,10 +255,11 @@ void Species::AutogenerationConfigurationChanged(void) // Then we loop through the GETypes and determine whether there are mutation types in use by a GEType // that are non-neutral, and if so, set the appropriate optimization flags. Normally these flags are - // sticky, but if there are no segregating mutations we can reset them and recover neutrality. + // sticky, but if there are no segregating mutations we can reset them and recover our initial state. if (registry_count == 0) { species_all_neutral_mutations_ = true; + species_no_neutral_mutations_ = true; for (Trait *trait : traits_) { @@ -277,7 +278,11 @@ void Species::AutogenerationConfigurationChanged(void) for (MutationType *mutation_type_ptr : ge_type_ptr->mutation_type_ptrs_) { - if (!mutation_type_ptr->all_neutral_DES_) + if (mutation_type_ptr->all_neutral_DES_) + { + species_no_neutral_mutations_ = false; + } + else // !mutation_type_ptr->all_neutral_DES_ { species_all_neutral_mutations_ = false; @@ -340,7 +345,12 @@ void Species::CheckOptimizationFlags(void) for (MutationType *mutation_type_ptr : ge_type_ptr->mutation_type_ptrs_) { - if (!mutation_type_ptr->all_neutral_DES_) + if (mutation_type_ptr->all_neutral_DES_) + { + if (species_no_neutral_mutations_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) species_no_neutral_mutations_ is incorrect (a neutral mutation type is in use)." << EidosTerminate(); + } + else // !mutation_type_ptr->all_neutral_DES_ { if (species_all_neutral_mutations_ != false) EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) species_all_neutral_mutations_ is incorrect (a non-neutral mutation type is in use)." << EidosTerminate(); @@ -365,7 +375,7 @@ void Species::CheckOptimizationFlags(void) } // Finally, check that all mutations in the registry are compatible with the flag settings. Note that this - // state is taken care of by _NoteNonNeutralMutation(), not by AutogenerationConfigurationChanged(). + // state is taken care of by NoteChangedMutation(), not by AutogenerationConfigurationChanged(). int registry_count = 0; const MutationIndex *registry_iter = population_.MutationRegistry(®istry_count); @@ -380,7 +390,12 @@ void Species::CheckOptimizationFlags(void) mut->SelfConsistencyCheck(" in Species::CheckOptimizationFlags()"); - if (!mut->is_neutral_for_all_traits_) + if (mut->is_neutral_for_all_traits_) + { + if (species_no_neutral_mutations_ != false) + EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) species_no_neutral_mutations_ is incorrect (a neutral mutation is segregating)." << EidosTerminate(); + } + else // !mut->is_neutral_for_all_traits_ { if (species_all_neutral_mutations_ != false) EIDOS_TERMINATION << "ERROR (Species::CheckOptimizationFlags): (internal error) species_all_neutral_mutations_ is incorrect (a non-neutral mutation is segregating)." << EidosTerminate(); @@ -415,34 +430,52 @@ void Species::CheckOptimizationFlags(void) } } -void Species::_NoteNonNeutralMutation(const Mutation *p_mut) +void Species::NoteChangedMutation(const Mutation *p_mut) { - // This is a fair bit of work, but it is only done once when a new non-neutral mutation is created, or a - // mutation is changed to a non-neutral state. That is not very frequent compared to other overhead. - - species_all_neutral_mutations_ = false; - p_mut->mutation_type_ptr_->muttype_all_neutral_mutations_ = false; - - // since the mutation is non-neutral for at least one trait, we have to fix the Trait flags - MutationTraitInfo *mut_trait_info = mutation_block_->TraitInfoForMutation(p_mut); - - for (Trait *trait : traits_) + if (p_mut->is_neutral_for_all_traits_) { - slim_trait_index_t trait_index = trait->Index(); - MutationTraitInfo &trait_info = mut_trait_info[trait_index]; + // We only need to know about mutations becoming neutral for tracking this species flag, which indicates + // that ALL mutations are non-neutral; if we see even one neutral mutation, this flag turns off. + species_no_neutral_mutations_ = false; + } + else + { + // We track a lot more information about mutations becoming non-neutral, because they need to be managed. + // This is a fair bit of work, but it is only done once when a new non-neutral mutation is created, or a + // mutation is changed to a non-neutral state. That is not very frequent compared to other overhead. + + species_all_neutral_mutations_ = false; + p_mut->mutation_type_ptr_->muttype_all_neutral_mutations_ = false; + + // since the mutation is non-neutral for at least one trait, we have to fix the Trait flags + MutationTraitInfo *mut_trait_info = mutation_block_->TraitInfoForMutation(p_mut); - if (trait_info.effect_size_ != (slim_effect_t)0.0) + for (Trait *trait : traits_) { - // this mutation is non-neutral for this trait - trait->trait_all_neutral_mutations_ = false; + slim_trait_index_t trait_index = trait->Index(); + MutationTraitInfo &trait_info = mut_trait_info[trait_index]; - if (!std::isnan(trait_info.dominance_coeff_UNSAFE_)) + if (trait_info.effect_size_ != (slim_effect_t)0.0) { - // this mutation is non-neutral *and* not independent-dominance - trait->trait_all_mutations_independent_dominance_ = false; + // this mutation is non-neutral for this trait + trait->trait_all_neutral_mutations_ = false; + + if (!std::isnan(trait_info.dominance_coeff_UNSAFE_)) + { + // this mutation is non-neutral *and* not independent-dominance + trait->trait_all_mutations_independent_dominance_ = false; + } } } } + + // Whenever a mutation changes in any way, whether becoming neutral or becoming non-neutral, we need + // to invalidate the nonneutral caches that that mutation is a member of. Right now we are massively + // over-conservative and simply invalidate ALL non-neutral caches with a global flag. + // FIXME MULTITRAIT should have per chromosome or even narrower flags +#if SLIM_USE_NONNEUTRAL_CACHES() + p_mut->mutation_type_ptr_->species_.all_nonneutral_caches_invalid_ = true; +#endif } void Species::PrepareForTraitCalculations(std::vector &mutationEffect_callbacks) @@ -471,22 +504,39 @@ void Species::PrepareForTraitCalculations(std::vector &mutation muttype->previous_is_pure_neutral_now_ = muttype->is_pure_neutral_now_; muttype->previous_subject_to_mutationEffect_callback_ = muttype->subject_to_mutationEffect_callback_; - muttype->previous_subject_to_non_neutral_callback_ = muttype->subject_to_non_neutral_callback_; + muttype->previous_subject_to_non_global_neutral_callback_ = muttype->subject_to_non_global_neutral_callback_; } } - // Initially, every mutation type is assumed to be uninfluenced by callbacks, and thus pure neutral - // if all mutations of that type are intrinsically neutral (i.e., have an effect of 0.0) +#if DEBUG + // Crosscheck to see whether mutation types and traits agree about whether all mutations are neutral. + // It is not clear to me whether this crosscheck should always pass, in the present design. + { + bool all_muttypes_all_neutral_mutations = true; + bool all_traits_all_neutral_mutations = true; + + for (auto muttype_iter : mut_types) + all_muttypes_all_neutral_mutations &= muttype_iter.second->muttype_all_neutral_mutations_; + for (Trait *trait : traits) + all_traits_all_neutral_mutations &= trait->trait_all_neutral_mutations_; + + if (all_muttypes_all_neutral_mutations != all_traits_all_neutral_mutations) + EIDOS_TERMINATION << "ERROR (Species::PrepareForTraitCalculations): (internal error) mutation types and traits disagree as to whether all mutations are neutral (mutation types say " << (all_muttypes_all_neutral_mutations ? "YES" : "NO") << ", traits say " << (all_traits_all_neutral_mutations ? "YES" : "NO") << ")." << EidosTerminate(); + } +#endif + + // Initially, every mutation type is assumed to be uninfluenced by callbacks, and thus pure-neutral + // if all mutations of that type are intrinsically neutral (i.e., have an effect of 0.0), which we track for (auto muttype_iter : mut_types) { MutationType *muttype = muttype_iter.second; muttype->is_pure_neutral_now_ = muttype->muttype_all_neutral_mutations_; muttype->subject_to_mutationEffect_callback_ = false; - muttype->subject_to_non_neutral_callback_ = false; + muttype->subject_to_non_global_neutral_callback_ = false; } - // Similarly, every trait is assumed to be uninfluenced by callbacks, and thus pure neutral if all + // Similarly, every trait is assumed to be uninfluenced by callbacks, and thus pure-neutral if all // mutations' effects are intrinsically neutral for that trait (i.e., have an effect of 0.0). For // traits we also determine pure independent dominance; similarly, every trait is assumed to be // uninfluenced by callbacks, and thus pure independent dominance if all mutations' effects exhibit @@ -496,7 +546,7 @@ void Species::PrepareForTraitCalculations(std::vector &mutation trait->is_pure_neutral_now_ = trait->trait_all_neutral_mutations_; trait->is_pure_independent_dominance_now_ = trait->trait_all_mutations_independent_dominance_ && !trait->trait_all_neutral_mutations_; trait->subject_to_mutationEffect_callback_ = false; - trait->subject_to_non_neutral_callback_ = false; + trait->subject_to_non_global_neutral_callback_ = false; } if (mutationEffect_callbacks.size() == 0) @@ -513,36 +563,64 @@ void Species::PrepareForTraitCalculations(std::vector &mutation for (SLiMEidosBlock *mutationEffect_callback : mutationEffect_callbacks) { MutationType *callback_mut_type = nullptr; - bool makes_neutral = _CallbackMakesMutationTypeNeutral(mutationEffect_callback, callback_mut_type); + bool makes_globally_neutral = _CallbackMakesMutationTypeGloballyNeutral(mutationEffect_callback, callback_mut_type); - // a callback might be defined for a muttype not in use, in which case callback_mut_type is nullptr + // a callback might be defined for a muttype or subpop not in use, in which case we skip it if (!callback_mut_type) continue; - if (makes_neutral) + if (makes_globally_neutral) { // the callback makes the mutation type completely neutral, in all subpops; this does not // entirely override a previous non-neutral callback, because that might have side effects // on aspects of the model of than the mutation effect, but effect is guaranteed neutral now - callback_mut_type->is_pure_neutral_now_ = true; - callback_mut_type->subject_to_mutationEffect_callback_ = true; + // if the mutation type is already pure neutral, the callback is redundant and is ignored + if (!callback_mut_type->is_pure_neutral_now_) + { + callback_mut_type->is_pure_neutral_now_ = true; + callback_mut_type->subject_to_mutationEffect_callback_ = true; + } } else { - // the callback has an effect that is something other than globally neutral, so the callback - // will have to be called, and it will override anything else going on, including previous - // callbacks in the callback chain (but might be overriden by a later callback in the chain) - callback_mut_type->is_pure_neutral_now_ = false; - callback_mut_type->subject_to_mutationEffect_callback_ = true; - callback_mut_type->subject_to_non_neutral_callback_ = true; + bool makes_non_neutral = _CallbackMakesMutationTypeNonNeutral(mutationEffect_callback, callback_mut_type); + + if (makes_non_neutral) + { + // the callback is non-neutral, so it has to be called, with non-neutral effects + callback_mut_type->is_pure_neutral_now_ = false; + callback_mut_type->subject_to_mutationEffect_callback_ = true; + callback_mut_type->subject_to_non_global_neutral_callback_ = true; + } + else + { + // the callback is neutral, just not global-neutral; so it might need to be called + if (callback_mut_type->is_pure_neutral_now_) + { + // the mutation type is pure-neutral, so the callback is redundant and can be + // ignored; all it does is set mutations to neutral that are already neutral + } + else + { + // the mutation type is not pure-neutral, so we have to consider the callback as + // "non-neutral" because it potentially modifies the existing effects of mutations + callback_mut_type->subject_to_mutationEffect_callback_ = true; + callback_mut_type->subject_to_non_global_neutral_callback_ = true; + } + } } } // And assess how traits are being influenced by mutationEffect() callbacks for (SLiMEidosBlock *mutationEffect_callback : mutationEffect_callbacks) { + MutationType *callback_mut_type = nullptr; Trait *callback_trait = nullptr; - bool makes_neutral = _CallbackMakesTraitNeutral(mutationEffect_callback, callback_trait); + bool makes_globally_neutral = _CallbackMakesTraitGloballyNeutral(mutationEffect_callback, callback_trait, callback_mut_type); + + // a callback might be defined for a muttype or subpop not in use, in which case we skip it + if (!callback_mut_type) + continue; // callbacks are always specific to one mutation type, so unless there is only one mutation type // defined, we can't easily infer that the trait has been made entirely neutral; we can just infer @@ -557,61 +635,86 @@ void Species::PrepareForTraitCalculations(std::vector &mutation // present; that would work, but then we'd often spend time making the independent dominance // cache and then not using it at all; ideally we'd be smarter about this. FIXME MULTICHROM - if (mut_types.size() == 1) + if (makes_globally_neutral) { - if (callback_trait) + if (mut_types.size() == 1) { - callback_trait->subject_to_mutationEffect_callback_ = true; - callback_trait->subject_to_non_neutral_callback_ = !makes_neutral; - callback_trait->is_pure_neutral_now_ = makes_neutral; - callback_trait->is_pure_independent_dominance_now_ = false; + // with just one mutation type, we can infer that the callback affects every mutation type, + // so we can draw a broader inference about the callback's effect on the model + for (Trait *affectedTrait : traits) + { + if (callback_trait && (affectedTrait != callback_trait)) + continue; + + // if the trait is already pure neutral, a callback making it neutral is redundant + if (!affectedTrait->is_pure_neutral_now_) + { + affectedTrait->subject_to_mutationEffect_callback_ = true; + affectedTrait->subject_to_non_global_neutral_callback_ = false; + affectedTrait->is_pure_neutral_now_ = true; + affectedTrait->is_pure_independent_dominance_now_ = false; + } + } } else { - // if no callback trait is defined for the callback, it affects all traits + // with more than one muttype, callbacks only make traits non-neutral; we don't try to track the + // possibility that multiple callbacks might together render a trait neutral again. as above, + // a callback never changes a non-independent-dominance trait into an independent-dominance trait. for (Trait *affectedTrait : traits) { - affectedTrait->subject_to_mutationEffect_callback_ = true; - affectedTrait->subject_to_non_neutral_callback_ = !makes_neutral; - affectedTrait->is_pure_neutral_now_ = makes_neutral; - affectedTrait->is_pure_independent_dominance_now_ = false; + if (callback_trait && (affectedTrait != callback_trait)) + continue; + + // if the trait is already pure neutral, a callback making it neutral is redundant + if (!affectedTrait->is_pure_neutral_now_) + { + affectedTrait->subject_to_mutationEffect_callback_ = true; + affectedTrait->is_pure_independent_dominance_now_ = false; + } } } } - else if (!makes_neutral) + else // if (!makes_globally_neutral) { - if (callback_trait) - { - callback_trait->subject_to_mutationEffect_callback_ = true; - callback_trait->subject_to_non_neutral_callback_ = true; - callback_trait->is_pure_neutral_now_ = false; - callback_trait->is_pure_independent_dominance_now_ = false; - } - else + bool makes_non_neutral = _CallbackMakesTraitNonNeutral(mutationEffect_callback, callback_trait, callback_mut_type); + + if (makes_non_neutral) { - // if no callback trait is defined for the callback, it affects all traits + // the callback is non-neutral, so it has to be called, with non-neutral effects for (Trait *affectedTrait : traits) { + if (callback_trait && (affectedTrait != callback_trait)) + continue; + affectedTrait->subject_to_mutationEffect_callback_ = true; - affectedTrait->subject_to_non_neutral_callback_ = true; + affectedTrait->subject_to_non_global_neutral_callback_ = true; affectedTrait->is_pure_neutral_now_ = false; affectedTrait->is_pure_independent_dominance_now_ = false; } } - } - else // if (makes_neutral) - { - // with more than one muttype, callbacks only make traits non-neutral; we don't try to track the - // possibility that multiple callbacks might together render a trait neutral again. as above, - // a callback never changes a non-independent-dominance trait into an independent-dominance trait. - if (callback_trait) - { - callback_trait->is_pure_independent_dominance_now_ = false; - } else { + // the callback is neutral, just not global-neutral; so it might need to be called for (Trait *affectedTrait : traits) - affectedTrait->is_pure_independent_dominance_now_ = false; + { + if (callback_trait && (affectedTrait != callback_trait)) + continue; + + if (affectedTrait->is_pure_neutral_now_) + { + // the trait is pure-neutral, so the callback is redundant and can be ignored; + // all it does is set mutations to neutral that are already neutral + } + else + { + // the trait is not pure-neutral, so we have to consider the callback "nonneutral" + // because it potentially modifies the existing effects of mutations + affectedTrait->subject_to_mutationEffect_callback_ = true; + affectedTrait->subject_to_non_global_neutral_callback_ = true; + affectedTrait->is_pure_independent_dominance_now_ = false; + } + } } } } @@ -626,7 +729,7 @@ void Species::PrepareForTraitCalculations(std::vector &mutation { MutationType *muttype = muttype_iter.second; - if (muttype->subject_to_non_neutral_callback_) + if (muttype->subject_to_non_global_neutral_callback_) { all_active_callbacks_are_global_neutral_effects = false; break; @@ -641,7 +744,7 @@ void Species::PrepareForTraitCalculations(std::vector &mutation // subject_to_mutationEffect_callback_ is set for a mutation type, all mutations of that type are // neutral, because the callback in question is global-neutral. When that flag is not set, we consult // the mutation itself to decide whether it goes into the non-neutral cache. - new_trait_calculation_regime = TraitCalculationRegime::kAllNeutralCallbacks; + new_trait_calculation_regime = TraitCalculationRegime::kAllGlobalNeutralCallbacks; } else { @@ -649,9 +752,9 @@ void Species::PrepareForTraitCalculations(std::vector &mutation // is at least one muttype that is influenced by a non-global-neutral callback. We will need to // take that into account. So we cannot assume that if subject_to_mutationEffect_callback_ is // set that mutation type is pure neutral; instead, we then have to additionally consult the - // subject_to_non_neutral_callback_ flag. If that is set, we have to call the non-global-neutral - // callback; if it is not set, it is subject to a callback that is not non-neutral, thus it is - // neutral, thus the mutation type is pure neutral. If subject_to_mutationEffect_callback_ is not + // subject_to_non_global_neutral_callback_ flag. If that is set, we have to call the non-global-neutral + // callback; if it is not set, it is subject to a callback that is not non-global-neutral, thus it is + // global-neutral, thus the mutation type is pure-neutral. If subject_to_mutationEffect_callback_ is not // set, that mutation type has no callback, and so we can again consult the mutation itself. // This regime is thus "we can't skip all callbacks, since some are non-global-neutral, so we have // to do an extra check and potentially actually call a callback." @@ -679,9 +782,10 @@ void Species::PrepareForTraitCalculations(std::vector &mutation // completely neutral, either because all mutations are intrinsically neutral and there are no callbacks, // or because all mutation types with non-neutral mutations are made neutral by global-neutral callbacks // and there are no other callbacks present (even overridden). We detect that special-case situation here. - // This regime is thus "all mutations are effectively neutral; genetics can be skipped." + // This regime is thus "all mutations are effectively neutral; genetics can be skipped." But note that + // baseline and individual offsets still affect traits, so we still have to include those. if ((new_trait_calculation_regime == TraitCalculationRegime::kNoActiveCallbacks) || - (new_trait_calculation_regime == TraitCalculationRegime::kAllNeutralCallbacks)) + (new_trait_calculation_regime == TraitCalculationRegime::kAllGlobalNeutralCallbacks)) { bool all_muttypes_are_pure_neutral = true; @@ -696,12 +800,26 @@ void Species::PrepareForTraitCalculations(std::vector &mutation } } +#if DEBUG + // Crosscheck to see whether mutation types and traits agree about whether the species is pure-neutral. + // It is not clear to me whether this crosscheck should always pass, in the present design. + { + bool all_traits_pure_neutral = true; + + for (Trait *trait : traits) + all_traits_pure_neutral &= trait->is_pure_neutral_now_; + + if (all_muttypes_are_pure_neutral != all_traits_pure_neutral) + EIDOS_TERMINATION << "ERROR (Species::PrepareForTraitCalculations): (internal error) mutation types and traits disagree as to whether the species is pure neutral (mutation types say " << (all_muttypes_are_pure_neutral ? "YES" : "NO") << ", traits say " << (all_traits_pure_neutral ? "YES" : "NO") << ")." << EidosTerminate(); + } +#endif + if (all_muttypes_are_pure_neutral) new_trait_calculation_regime = TraitCalculationRegime::kPureNeutral; } if (new_trait_calculation_regime == TraitCalculationRegime::kUndefined) - EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) nonneutral regime was not decided." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Species::PrepareForTraitCalculations): (internal error) nonneutral regime was not decided." << EidosTerminate(); if (new_trait_calculation_regime != TraitCalculationRegime::kPureNeutral) { @@ -710,8 +828,8 @@ void Species::PrepareForTraitCalculations(std::vector &mutation // non-neutral and non-independent-dominance for other traits, and because we need the non-neutral cache // for the hemizygous case anyway. It does mean that mutations in the non-neutral cache will additionally // be summarized for their independent-dominance effects, after the non-neutral cache itself is built. - // We do not do this if we are in the "pure neutral" regime, since genetic calculations are not needed. - // Similarly, we do not do it for any traits that are themselves "pure neutral". + // We do not do this if we are in the "pure-neutral" regime, since genetic calculations are not needed. + // Similarly, we do not do it for any traits that are themselves "pure-neutral". for (Trait *trait : traits) if (trait->is_pure_independent_dominance_now_) pure_independent_dominance_traits.push_back(trait->Index()); @@ -743,7 +861,7 @@ void Species::PrepareForTraitCalculations(std::vector &mutation #endif } -bool Species::_CallbackMakesMutationTypeNeutral(SLiMEidosBlock *mutationEffect_callback, MutationType *&mut_type_ptr_ref) +bool Species::_CallbackMakesMutationTypeGloballyNeutral(SLiMEidosBlock *mutationEffect_callback, MutationType *&mut_type_ptr_ref) { // This method checks whether a mutationEffect() callback makes the mutation type that it refers to neutral. // The callback has to apply globally: to all subpopulations, and to all traits. We could try to piece the @@ -753,10 +871,29 @@ bool Species::_CallbackMakesMutationTypeNeutral(SLiMEidosBlock *mutationEffect_c #if DEBUG if (mutation_type_id == -1) - EIDOS_TERMINATION << "ERROR (Population::_CallbackMakesMutationTypeNeutral): (internal error) mutationEffect() callback has no mutation type id." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Population::_CallbackMakesMutationTypeGloballyNeutral): (internal error) mutationEffect() callback has no mutation type id." << EidosTerminate(); #endif - bool makes_neutral = false; // we consider it non-neutral if it doesn't apply to all subpops and traits + // if the callback refers to a subpop that doesn't currently exist, it doesn't make anything neutral + if (mutationEffect_callback->subpopulation_id_ != -1) + { + Subpopulation *subpop = SubpopulationWithID(mutationEffect_callback->subpopulation_id_); + + if (!subpop) + { + mut_type_ptr_ref = nullptr; + return false; + } + } + + // find the callback's mutation type; this could be nullptr, if the referenced muttype has not been defined + mut_type_ptr_ref = MutationTypeWithID(mutation_type_id); + + // if the callback refers to a mutation type that is not defined, it doesn't make anything neutral + if (!mut_type_ptr_ref) + return false; + + bool makes_neutral = false; // it is non-global-neutral if it doesn't apply to all subpops and traits if ((mutationEffect_callback->subpopulation_id_ == -1) && (mutationEffect_callback->trait_index_ == -1)) { @@ -800,16 +937,143 @@ bool Species::_CallbackMakesMutationTypeNeutral(SLiMEidosBlock *mutationEffect_c } } + return makes_neutral; +} + +bool Species::_CallbackMakesMutationTypeNonNeutral(SLiMEidosBlock *mutationEffect_callback, MutationType *&mut_type_ptr_ref) +{ + // This method checks whether a mutationEffect() callback makes the mutation type it refers to nonneutral. + // The callback can apply to any/all subpopulations/traits; in any case it renders the muttype nonneutral. + // Note that there is an in-between space between this method and the previous one, when a callback returns + // a constant neutral value, but does not apply to all subpopulations/traits. + slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; + +#if DEBUG + if (mutation_type_id == -1) + EIDOS_TERMINATION << "ERROR (Population::_CallbackMakesMutationTypeNonNeutral): (internal error) mutationEffect() callback has no mutation type id." << EidosTerminate(); +#endif + + // if the callback refers to a subpop that doesn't currently exist, it doesn't make anything non-neutral + if (mutationEffect_callback->subpopulation_id_ != -1) + { + Subpopulation *subpop = SubpopulationWithID(mutationEffect_callback->subpopulation_id_); + + if (!subpop) + { + mut_type_ptr_ref = nullptr; + return false; + } + } + // find the callback's mutation type; this could be nullptr, if the referenced muttype has not been defined mut_type_ptr_ref = MutationTypeWithID(mutation_type_id); - return makes_neutral; + // if the callback refers to a mutation type that is not defined, it doesn't make anything non-neutral + if (!mut_type_ptr_ref) + return false; + + bool makes_neutral = false; // it is non-neutral if it doesn't have a cached return value + Trait *affected_trait = nullptr; + + if (mutationEffect_callback->trait_index_ != -1) + affected_trait = traits_[mutationEffect_callback->trait_index_]; + + const EidosASTNode *compound_statement_node = mutationEffect_callback->compound_statement_node_; + + if (compound_statement_node->cached_return_value_) + { + // The script is a constant expression such as "{ return 1.1; }" + EidosValue *result = compound_statement_node->cached_return_value_.get(); + + if ((result->Type() == EidosValueType::kValueFloat) && (result->Count() == 1)) + { + if (result->FloatData()[0] == 1.0) + { + // the callback returns 1.0; this makes the muttype neutral if all traits are multiplicative + makes_neutral = true; + + if (affected_trait) + { + if (affected_trait->Type() == TraitType::kAdditive) + makes_neutral = false; + } + else + { + for (Trait *trait : traits_) + if (trait->Type() == TraitType::kAdditive) { + makes_neutral = false; + break; + } + } + } + else if (result->FloatData()[0] == 0.0) + { + // the callback returns 0.0; this makes the muttype neutral if all traits are additive + makes_neutral = true; + + if (affected_trait) + { + if (affected_trait->Type() == TraitType::kMultiplicative) + makes_neutral = false; + } + else + { + for (Trait *trait : traits_) + if (trait->Type() == TraitType::kMultiplicative) { + makes_neutral = false; + break; + } + } + } + } + else if (result->Type() == EidosValueType::kValueNULL) + { + // the callback returns NULL; this makes the muttype neutral in all cases + makes_neutral = true; + } + } + + return !makes_neutral; } -bool Species::_CallbackMakesTraitNeutral(SLiMEidosBlock *mutationEffect_callback, Trait *&trait_ptr_ref) +bool Species::_CallbackMakesTraitGloballyNeutral(SLiMEidosBlock *mutationEffect_callback, Trait *&trait_ptr_ref, MutationType *&mut_type_ptr_ref) { // This method checks whether a mutationEffect() callback makes the trait that it refers to neutral. // The callback has to apply globally to all subpopulations (but not necessarily to all traits). + slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; + +#if DEBUG + if (mutation_type_id == -1) + EIDOS_TERMINATION << "ERROR (Population::_CallbackMakesTraitGloballyNeutral): (internal error) mutationEffect() callback has no mutation type id." << EidosTerminate(); +#endif + + // find the callback's trait; this could be nullptr, if the callback is not specific to one trait + slim_trait_index_t trait_index = mutationEffect_callback->trait_index_; + + if (trait_index == -1) + trait_ptr_ref = nullptr; + else + trait_ptr_ref = traits_[trait_index]; + + // if the callback refers to a subpop that doesn't currently exist, it doesn't make anything neutral + if (mutationEffect_callback->subpopulation_id_ != -1) + { + Subpopulation *subpop = SubpopulationWithID(mutationEffect_callback->subpopulation_id_); + + if (!subpop) + { + mut_type_ptr_ref = nullptr; + return false; + } + } + + // find the callback's mutation type; this could be nullptr, if the referenced muttype has not been defined + mut_type_ptr_ref = MutationTypeWithID(mutation_type_id); + + // if the callback refers to a mutation type that is not defined, it doesn't make anything non-neutral + if (!mut_type_ptr_ref) + return false; + bool makes_neutral = false; // we consider it non-neutral if it doesn't apply to all subpops if (mutationEffect_callback->subpopulation_id_ == -1) @@ -854,7 +1118,23 @@ bool Species::_CallbackMakesTraitNeutral(SLiMEidosBlock *mutationEffect_callback } } - // find the callback's trait; this could be nullptr, if the referenced muttype has not been defined + return makes_neutral; +} + +bool Species::_CallbackMakesTraitNonNeutral(SLiMEidosBlock *mutationEffect_callback, Trait *&trait_ptr_ref, MutationType *&mut_type_ptr_ref) +{ + // This method checks whether a mutationEffect() callback makes the trait that it refers to nonneutral. + // The callback can apply to any/all subpopulations/traits; in any case it renders the muttype nonneutral. + // Note that there is an in-between space between this method and the previous one, when a callback returns + // a constant neutral value, but does not apply to all subpopulations. + slim_objectid_t mutation_type_id = mutationEffect_callback->mutation_type_id_; + +#if DEBUG + if (mutation_type_id == -1) + EIDOS_TERMINATION << "ERROR (Population::_CallbackMakesTraitNonNeutral): (internal error) mutationEffect() callback has no mutation type id." << EidosTerminate(); +#endif + + // find the callback's trait; this could be nullptr, if the callback is not specific to one trait slim_trait_index_t trait_index = mutationEffect_callback->trait_index_; if (trait_index == -1) @@ -862,7 +1142,66 @@ bool Species::_CallbackMakesTraitNeutral(SLiMEidosBlock *mutationEffect_callback else trait_ptr_ref = traits_[trait_index]; - return makes_neutral; + // if the callback refers to a subpop that doesn't currently exist, it doesn't make anything nonneutral + if (mutationEffect_callback->subpopulation_id_ != -1) + { + Subpopulation *subpop = SubpopulationWithID(mutationEffect_callback->subpopulation_id_); + + if (!subpop) + { + mut_type_ptr_ref = nullptr; + return false; + } + } + + // find the callback's mutation type; this could be nullptr, if the referenced muttype has not been defined + mut_type_ptr_ref = MutationTypeWithID(mutation_type_id); + + // if the callback refers to a mutation type that is not defined, it doesn't make anything non-neutral + if (!mut_type_ptr_ref) + return false; + + bool makes_neutral = false; // it is non-neutral if it doesn't have a cached return value + const EidosASTNode *compound_statement_node = mutationEffect_callback->compound_statement_node_; + + if (compound_statement_node->cached_return_value_) + { + // The script is a constant expression such as "{ return 1.1; }" + EidosValue *result = compound_statement_node->cached_return_value_.get(); + + if ((result->Type() == EidosValueType::kValueFloat) && (result->Count() == 1)) + { + if (result->FloatData()[0] == 1.0) + { + // the callback returns 1.0; this makes the muttype neutral if all traits are multiplicative + makes_neutral = true; + + for (Trait *trait : traits_) + if (trait->Type() == TraitType::kAdditive) { + makes_neutral = false; + break; + } + } + else if (result->FloatData()[0] == 0.0) + { + // the callback returns 0.0; this makes the muttype neutral if all traits are additive + makes_neutral = true; + + for (Trait *trait : traits_) + if (trait->Type() == TraitType::kMultiplicative) { + makes_neutral = false; + break; + } + } + } + else if (result->Type() == EidosValueType::kValueNULL) + { + // the callback returns NULL; this makes the muttype neutral in all cases + makes_neutral = true; + } + } + + return !makes_neutral; } #if SLIM_USE_NONNEUTRAL_CACHES() @@ -886,6 +1225,7 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul { // This regime is "all mutations are effectively neutral; genetics can be skipped". // MutationType flags therefore don't matter; whatever the situation might be, we're skipping it all. + // Note that baseline and individual offsets still need to be included, however. } else if (current_trait_calculation_regime_ == TraitCalculationRegime::kNoActiveCallbacks) { @@ -893,7 +1233,7 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul // matter; just look at the mutations themselves to determine which go into the non-neutral cache". // MutationType flags therefore don't matter. If mutations changed, that invalidated those runs. } - else if (current_trait_calculation_regime_ == TraitCalculationRegime::kAllNeutralCallbacks) + else if (current_trait_calculation_regime_ == TraitCalculationRegime::kAllGlobalNeutralCallbacks) { // This regime is "we can skip all callbacks, since they are all global-neutral on some or all mutation // types". The *same* mutation types need to be made global-neutral as last time, however, for us to @@ -918,7 +1258,7 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul { // This regime is "we can't skip all callbacks, since some are non-global-neutral, so we have to do an // extra check and potentially actually call a callback." Here, subject_to_mutationEffect_callback_ - // and subject_to_non_neutral_callback_ both have to be the same for us to carry over nonneutral caches. + // and subject_to_non_global_neutral_callback_ both have to be the same for us to carry over nonneutral caches. bool callback_state_identical = true; for (auto muttype_iter : mut_types) @@ -927,7 +1267,7 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul if (muttype->subject_to_mutationEffect_callback_ != muttype->previous_subject_to_mutationEffect_callback_) callback_state_identical = false; - if (muttype->subject_to_non_neutral_callback_ != muttype->previous_subject_to_non_neutral_callback_) + if (muttype->subject_to_non_global_neutral_callback_ != muttype->previous_subject_to_non_global_neutral_callback_) callback_state_identical = false; } @@ -979,8 +1319,8 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNoActiveCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNonNeutralCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); @@ -991,8 +1331,8 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNoActiveCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNonNeutralCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); @@ -1005,8 +1345,8 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNoActiveCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNonNeutralCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); @@ -1017,8 +1357,8 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNoActiveCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNonNeutralCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); @@ -1036,8 +1376,8 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNoActiveCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNonNeutralCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); @@ -1048,8 +1388,8 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNoActiveCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNonNeutralCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); @@ -1062,8 +1402,8 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNoActiveCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNonNeutralCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); @@ -1074,8 +1414,8 @@ void Species::_ValidateNonNeutralCaches(TraitCalculationRegime last_trait_calcul _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNoActiveCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; - case TraitCalculationRegime::kAllNeutralCallbacks: - _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: + _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; case TraitCalculationRegime::kNonNeutralCallbacks: _ValidateNonNeutralCachesForMutationRunPool_TEMPLATED = &Species::_ValidateNonNeutralCachesForMutationRunPool; break; default: EIDOS_TERMINATION << "ERROR (Species::ValidateNonNeutralCaches): (internal error) unrecognized regime." << EidosTerminate(); @@ -1133,10 +1473,10 @@ int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_ { switch (f_nonneutral_cache_regime) { - case TraitCalculationRegime::kPureNeutral: mutrun->cache_nonneutral_mutations_REGIME_0(IndependentDominanceCacheCount()); break; - case TraitCalculationRegime::kNoActiveCallbacks: mutrun->cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr, IndependentDominanceCacheCount()); break; - case TraitCalculationRegime::kAllNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr, IndependentDominanceCacheCount()); break; - case TraitCalculationRegime::kNonNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr, IndependentDominanceCacheCount()); break; + case TraitCalculationRegime::kPureNeutral: mutrun->cache_nonneutral_mutations_REGIME_0(IndependentDominanceCacheCount()); break; + case TraitCalculationRegime::kNoActiveCallbacks: mutrun->cache_nonneutral_mutations_REGIME_1(p_mut_block_ptr, IndependentDominanceCacheCount()); break; + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_2(p_mut_block_ptr, IndependentDominanceCacheCount()); break; + case TraitCalculationRegime::kNonNeutralCallbacks: mutrun->cache_nonneutral_mutations_REGIME_3(p_mut_block_ptr, IndependentDominanceCacheCount()); break; default: EIDOS_TERMINATION << "ERROR (Species::_ValidateNonNeutralCachesForMutationRunPool): (internal error) unrecognized regime." << EidosTerminate(); } @@ -1180,35 +1520,35 @@ int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &p_ template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); -template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); +template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); template int64_t Species::_ValidateNonNeutralCachesForMutationRunPool(MutationRunPool &, Mutation *, std::vector &); #endif // SLIM_USE_NONNEUTRAL_CACHES() @@ -3895,6 +4235,7 @@ void Species::RunInitializeCallbacks(void) inddom_cache_count_ = static_cast(static_cast(inddom_cache_count_) + 1); } +#if DEBUG_TRAIT_DEMAND() std::cout << "### initialize(): " << static_cast(inddom_cache_count_) << " traits will receive independent-dominance cache space"; if (static_cast(inddom_cache_count_) > 0) { @@ -3905,6 +4246,7 @@ void Species::RunInitializeCallbacks(void) std::cout << "}"; } std::cout << std::endl; +#endif // DEBUG_TRAIT_DEMAND() #endif // SLIM_USE_INDEPENDENT_DOMINANCE_CACHES() #endif // SLIM_USE_NONNEUTRAL_CACHES() diff --git a/core/species.h b/core/species.h index 4fa7ec38..b00f4a47 100644 --- a/core/species.h +++ b/core/species.h @@ -403,13 +403,20 @@ class Species : public EidosDictionaryUnretained bool has_recalculated_fitness_ = false; // set to true when recalculateFitness() is called, so we know fitness values are valid - // optimization of the pure neutral case; this is set to false if (a) a non-neutral mutation is added by the user, (b) a genomic element type is configured to use a - // non-neutral mutation type, (c) an already existing mutation type (assumed to be in use) is set to a non-neutral DES, or (d) a mutation's selection coefficient is - // changed to non-neutral. The flag is never set back to true. Importantly, simply defining a non-neutral mutation type does NOT clear this flag; we want sims to be - // able to run a neutral burn-in at full speed, only slowing down when the non-neutral mutation type is actually used. BCH 12 January 2018: Also, note that this flag - // is unaffected by the fitness_scaling_ properties on Subpopulation and Individual, which are taken into account even when this flag is set. + // optimization of the neutral case; this is set to false if (a) a non-neutral mutation is added by the user, (b) a genomic element type is configured to use a + // non-neutral mutation type, (c) an already existing mutation type (assumed to be in use) is set to a non-neutral DES, or (d) a mutation's effect size is changed + // changed to non-neutral. The flag is set back to true only with an empty mutation registry; we do not sweep the registry to check and reset this flag. Note that + // simply defining a non-neutral mutation type does NOT clear this flag; we want only be influenced by mutation types that are actually in use. This flag does NOT + // factor in mutationEffect() callbacks; to conclude that the simulation is actually pure-neutral, those have to be considered also. bool species_all_neutral_mutations_ = true; // optimization flag + // optimization of the non-neutral case; this is set to false if (a) a neutral mutation is added by the user, (b) a genomic element type is configured to use + // a neutral mutation type, (c) an already existing mutation type (assumed to be in use) is set to a neutral DES, or (d) a mutation's effect size is changed + // changed to neutral. The flag is set back to true only with an empty mutation registry; we do not sweep the registry to check and reset this flag. Note that + // simply defining a neutral mutation type does NOT clear this flag; we want only be influenced by mutation types that are actually in use. This flag does NOT + // factor in mutationEffect() callbacks; to conclude that the simulation is actually pure-non-neutral, those have to be considered also. + bool species_no_neutral_mutations_ = true; // optimization flag + // this flag tracks whether a type 's' mutation type has ever been seen; we just set it to true if we see one, we never set it back to false again, for simplicity // this switches to a less optimized case when evolving in WF models, if a type 's' DES could be present, since that can open up various cans of worms bool type_s_DESs_present_ = false; // optimization flag @@ -469,29 +476,18 @@ class Species : public EidosDictionaryUnretained } } - void _NoteNonNeutralMutation(const Mutation *p_mut); - inline __attribute__((always_inline)) void NoteChangedMutation(const Mutation *p_mut) - { - // NoteChangedMutation() should be called whenever a mutation is added/changed in a way that is not - // driven by SLiM's auto-generation configuration (i.e., GenomicElementType and MutationType). The - // characteristics of this kind of mutation are not known, so they have to be examined closely. - if (!p_mut->is_neutral_for_all_traits_) - _NoteNonNeutralMutation(p_mut); - - // This needs to be done in the neutral case too; for example, a non-neutral mutation might - // have been changed into a neutral mutation, which invalidates our non-neutral caches -#if SLIM_USE_NONNEUTRAL_CACHES() - p_mut->mutation_type_ptr_->species_.all_nonneutral_caches_invalid_ = true; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags -#endif - } + // This must be called whenever a mutation changes state for any reason, to keep optimization flags correct. + void NoteChangedMutation(const Mutation *p_mut); // PrepareForTraitCalculations() is the funnel method to be called before using trait values, for example to // calculate individual fitness. It determines the trait calculation regime (see TraitCalculationRegime), // invalidates caches as needed, then validates nonneutral and independent dominance caches as needed. It is // called by demandPhenotype(), demandPhenotypeForIndividuals(), and RecalculateFitness(). void PrepareForTraitCalculations(std::vector &mutationEffect_callbacks); - bool _CallbackMakesMutationTypeNeutral(SLiMEidosBlock *mutationEffect_callback, MutationType *&mut_type_ptr_ref); - bool _CallbackMakesTraitNeutral(SLiMEidosBlock *mutationEffect_callback, Trait *&trait_ptr_ref); + bool _CallbackMakesMutationTypeGloballyNeutral(SLiMEidosBlock *mutationEffect_callback, MutationType *&mut_type_ptr_ref); + bool _CallbackMakesMutationTypeNonNeutral(SLiMEidosBlock *mutationEffect_callback, MutationType *&mut_type_ptr_ref); + bool _CallbackMakesTraitGloballyNeutral(SLiMEidosBlock *mutationEffect_callback, Trait *&trait_ptr_ref, MutationType *&mut_type_ptr_ref); + bool _CallbackMakesTraitNonNeutral(SLiMEidosBlock *mutationEffect_callback, Trait *&trait_ptr_ref, MutationType *&mut_type_ptr_ref); #if SLIM_USE_NONNEUTRAL_CACHES() // Validates the MutationRun nonneutral caches across the species. Called by PrepareForTraitCalculations(). diff --git a/core/species_eidos.cpp b/core/species_eidos.cpp index 2838188a..54d338e4 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -2367,6 +2367,57 @@ EidosValue_SP Species::GetProperty(EidosGlobalStringID p_property_id) return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(tag_value)); } + + // internal, not user-visible, used for unit tests; most are only available in DEBUG builds + case gID__debugBuild: + { + // This property has value T in a DEBUG build, F otherwise. +#if DEBUG + return gStaticEidosValue_LogicalT; +#else + return gStaticEidosValue_LogicalF; +#endif + } +#if DEBUG + case gID__allocatedNonneutralCacheCount: + { + // This property provides a count of the number of nonneutral caches that have been allocated + int64_t allocated_count = 0; + + for (Chromosome *chromosome : chromosomes_) + { + int context_count = chromosome->ChromosomeMutationRunContextCount(); + + for (int context_index = 0; context_index < context_count; ++context_index) + { + MutationRunContext &context = chromosome->ChromosomeMutationRunContextForThread(context_index); + MutationRunPool in_use_pool = context.in_use_pool_; + MutationRunPool freed_pool = context.freed_pool_; + + for (const MutationRun *mutrun : in_use_pool) + if (mutrun->nonneutral_cache_ != nullptr) + allocated_count++; + + for (const MutationRun *mutrun : freed_pool) + if (mutrun->nonneutral_cache_ != nullptr) + allocated_count++; + } + } + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Int(allocated_count)); + } + case gID__traitCalculationRegimeName: + { + switch (current_trait_calculation_regime_) + { + case TraitCalculationRegime::kUndefined: return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("kUndefined")); + case TraitCalculationRegime::kPureNeutral: return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("kPureNeutral")); + case TraitCalculationRegime::kNoActiveCallbacks: return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("kNoActiveCallbacks")); + case TraitCalculationRegime::kAllGlobalNeutralCallbacks: return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("kAllGlobalNeutralCallbacks")); + case TraitCalculationRegime::kNonNeutralCallbacks: return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String("kNonNeutralCallbacks")); + } + } +#endif // all others, including gID_none default: @@ -4866,6 +4917,13 @@ std::vector *Species_Class::Properties_MUTABLE(void) properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_tag, false, kEidosValueMaskInt | kEidosValueMaskSingleton))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_traits, true, kEidosValueMaskObject, gSLiM_Trait_Class))); + // internal properties, not user-visible + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr__debugBuild, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))); +#if DEBUG + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr__allocatedNonneutralCacheCount, true, kEidosValueMaskInt | kEidosValueMaskSingleton))); + properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr__traitCalculationRegimeName, true, kEidosValueMaskString | kEidosValueMaskSingleton))); +#endif + std::sort(properties->begin(), properties->end(), CompareEidosPropertySignatures); } @@ -4921,6 +4979,8 @@ const std::vector *Species_Class::Methods(void) const methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_treeSeqSimplify, kEidosValueMaskVOID))); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_treeSeqRememberIndividuals, kEidosValueMaskVOID))->AddObject("individuals", gSLiM_Individual_Class)->AddLogical_OS("permanent", gStaticEidosValue_LogicalT)); methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_treeSeqOutput, kEidosValueMaskVOID))->AddString_S("path")->AddLogical_OS("simplify", gStaticEidosValue_LogicalT)->AddLogical_OS("includeModel", gStaticEidosValue_LogicalT)->AddObject_OSN("metadata", gEidosDictionaryUnretained_Class, gStaticEidosValueNULL)->AddLogical_OS("overwriteDirectory", gStaticEidosValue_LogicalF)); + + // internal methods, not user-visible methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr__debug, kEidosValueMaskVOID))); std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); diff --git a/core/subpopulation.cpp b/core/subpopulation.cpp index 778ca045..a6600419 100644 --- a/core/subpopulation.cpp +++ b/core/subpopulation.cpp @@ -1324,47 +1324,129 @@ slim_refcount_t Subpopulation::NullHaplosomeCount(void) // calls UpdateFitness() on each subpopulation. This method expresses demand for the traits in question, and // then produces fitness values by factoring in fitnessEffect() callbacks and fitnessScaling values. It stores // the fitness values in the appropriate places to prepare for their later use. -void Subpopulation::UpdateFitness(std::vector &p_subpop_mutationEffect_callbacks, std::vector &p_subpop_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation) +void Subpopulation::UpdateFitness(const std::vector &p_subpop_mutationEffect_callbacks, const std::vector &p_subpop_fitnessEffect_callbacks, const std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation) { - // Determine whether we are in a "pure neutral" case where we don't need to calculate individual fitness - // because all individuals have neutral fitness. The simplest case where this is true is if there are no + if (p_direct_effect_trait_indices.size() > 0) + { +#if DEBUG_TRAIT_DEMAND() + std::cout << "# " << community_.Tick() << " --- UpdateFitness() called with direct-fitness-effect traits {"; + for (slim_trait_index_t trait_index : p_direct_effect_trait_indices) + std::cout << " " << species_.Traits()[trait_index]->Name(); + std::cout << " } in subpop p" << subpopulation_id_ << ", forceRecalc == " << (p_force_trait_recalculation ? "T" : "F") << std::endl; +#endif + } + else + { +#if DEBUG_TRAIT_DEMAND() + std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding NO traits in subpop p" << subpopulation_id_ << std::endl; +#endif + } + + // Determine whether we're in a "super-pure-neutral" case where we don't need to calculate individual fitness + // because all individuals have a constant fitness. The obvious case where this is true is if there are no // traits with a direct fitness effect, no non-neutral subpopulation or individual fitnessScaling effects, // and no active mutationEffect() or fitnessEffect() callbacks. There are more subtle ways it can be true - // also: active callbacks are OK as long as they return a constant, neutral value; and traits with a direct + // also: active callbacks are OK as long as they return a constant, neutral value. Any fitnessScaling value + // for the subpopulation is actually fine since it produces a constant effect. Also, traits with a direct // fitness effect are OK as long as either (a) all mutations have a neutral effect upon that particular trait, // or (b) the trait value doesn't matter because it is overridden by an active mutationEffect() callback - // with a constant, neutral value. We evaluate this for each subpopulation because different subpopulations - // can have different active callbacks, and because the callbacks in other subpopulations might active or + // with a constant, neutral value -- but in either case, baseline and individual offsets must also be constant + // for all traits for this to apply. We evaluate this for each subpopulation because different subpopulations + // can have different active callbacks, and because the callbacks in other subpopulations might activate or // deactivate the callbacks in this subpopulation; we therefore need to figure it out right here at this - // moment. Even if we decide that we are not "pure neutral", we might deduce that a particular trait with a - // direct fitness effect is not relevant to fitness in this subpopulation, for the above reasons; in that - // case, we still want to remove it from the set of traits that we demand and evaluate, for efficiency. And - // we might decide that a given trait is relevant to fitness, but has a constant effect for all individuals; - // we then want to remove that trait, but factor that constant effect in. + // moment. Even if we decide that we are not "super-pure-neutral", we might deduce that a particular trait + // with a direct fitness effect is not relevant to fitness in this subpopulation, for the above reasons; in + // that case, we still want to remove it from the set of traits that we demand and evaluate, for efficiency. + // We might also decide that a given trait is relevant to fitness, with a non-neutral direct fitness effect, + // but has a constant effect for every individual; we again want to remove that trait from the set of traits + // that we demand and evaluate, but we need to factor that constant effect in. slim_fitness_t subpop_fitness_scaling = (slim_fitness_t)subpop_fitness_scaling_; // guaranteed >= 0.0 - bool has_constant_fitness = true; + slim_fitness_t constant_fitness_effects = subpop_fitness_scaling; // including all constant fitness effects #ifdef SLIMGUI double constant_unscaled_fitness_value = 1.0; // without subpopulation fitnessScaling #endif - double constant_fitness_value = (double)subpop_fitness_scaling; // with all fitness effects incorporated - if (true) - has_constant_fitness = false; // for now, assume this is false since we don't know + // We make a private copy of p_direct_effect_trait_indices here, so we can remove constant-effect traits. + std::vector direct_effect_trait_indices = p_direct_effect_trait_indices; - if (has_constant_fitness) + // We assume constant fitness until proven otherwise -- which is what we are about to determine. + bool has_constant_fitness = true; + slim_trait_index_t trait_indices_count = (slim_trait_index_t)direct_effect_trait_indices.size(); + + for (slim_trait_index_t trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) { - // we know this subpopulation has effectively constant fitness; we therefore don't express demand for - // any traits, which means trait may keep NAN values even if the traits have a direct fitness effect + slim_trait_index_t direct_effect_trait_index = direct_effect_trait_indices[trait_indices_index]; + Trait *direct_effect_trait = species_.Traits()[direct_effect_trait_index]; + +#if DEBUG + if (!direct_effect_trait->HasDirectFitnessEffect()) + EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): (internal error) direct-fitness trait does not, in fact, have a direct fitness effect." << EidosTerminate(nullptr); +#endif + + // First of all, being pure neutral is required for a trait to be constant-fitness-effect. + if (!direct_effect_trait->is_pure_neutral_now_) + { + has_constant_fitness = false; + continue; // other traits might be removable, even if we're not "super-pure-neutral" + } + + // Second, all individuals must have the same individual offset value. We check that based upon two + // things: one, if an offset has ever been set in script or the offset distribution has ever changed, + // then we assume it is not true; and two, if the offset distribution has a non-zero standard deviation, + // we assume it is not true. This is conservative; it could be that the script changes offset values + // for everyone, but to the same constant value (particularly within a single subpopulation), but we + // don't detect that; the trait will not be seen as super-pure-neutral in that case. + if (direct_effect_trait->IndividualOffsetEverChanged() || + (direct_effect_trait->IndividualOffsetDistributionSD() != 0.0)) + { + has_constant_fitness = false; + continue; // other traits might be removable, even if we're not "super-pure-neutral" + } + + // This trait satisfies all of the "super-pure-neutral" constraints, so it has a constant fitness effect, + // calculated here, based on the baseline offset and constant individual offset of the trait. + slim_fitness_t trait_constant_effect = direct_effect_trait->BaselineOffset(); + slim_trait_offset_t constant_offset_value = direct_effect_trait->DrawIndividualOffset(); // this will return the fixed individual offset, including the exp() transform if it is a multiplicative trait + + if (direct_effect_trait->Type() == TraitType::kMultiplicative) + trait_constant_effect *= constant_offset_value; + else + trait_constant_effect += constant_offset_value; + + // We are removing this super-pure-neutral trait from processing, so we incorporate its fitness effect. + constant_fitness_effects *= trait_constant_effect; + #if DEBUG_TRAIT_DEMAND() - std::cout << "# " << community_.Tick() << " --- UpdateFitness() determined constant fitness of " << constant_fitness_value << std::endl; + std::cout << "# " << community_.Tick() << " --- UpdateFitness() removed super-pure-neutral trait '" << direct_effect_trait->Name() << "' with constant fitness effect " << trait_constant_effect << std::endl; #endif + // Remove the element at index demanded_trait_indices_index, decrement the count, and do this index again. + // Note that this modifies the vector of demanded trait indices supplied by the caller; this is by design. + direct_effect_trait_indices.erase(direct_effect_trait_indices.begin() + trait_indices_index); + trait_indices_count--; + trait_indices_index--; + } + + // If there are fitnessEffect() callbacks, we need to evaluate fitness. This would not be true if the + // callbacks were all constant-effect -- but why would one write a constant-effect fitnessEffect() callback? + if (p_subpop_fitnessEffect_callbacks.size() > 0) + has_constant_fitness = false; + + if (has_constant_fitness) + { + // We know this subpopulation has effectively constant fitness; we therefore don't express demand for + // any traits, which means traits may keep NAN values even if the traits have a direct fitness effect. + if (model_type_ == SLiMModelType::kModelTypeWF) { - // in WF models we can take advantage of constant fitness to completely remove individual-level - // fitness bookkeeping with the individual_cached_fitness_OVERRIDE_ mechanism + // In WF models we can take advantage of constant fitness to completely remove individual-level + // fitness bookkeeping with the individual_cached_fitness_OVERRIDE_ mechanism. individual_cached_fitness_OVERRIDE_ = true; - individual_cached_fitness_OVERRIDE_value_ = constant_fitness_value; + individual_cached_fitness_OVERRIDE_value_ = constant_fitness_effects; + +#if DEBUG_TRAIT_DEMAND() + std::cout << "# " << community_.Tick() << " --- UpdateFitness() determined a constant fitness of " << constant_fitness_effects << "; setting individual_cached_fitness_OVERRIDE_value_ for subpop p" << subpopulation_id_ << std::endl; +#endif #ifdef SLIMGUI for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) @@ -1373,9 +1455,13 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio } else // (model_type_ == SLiMModelType::kModelTypeNonWF) { - // in nonWF models we are required to fill in the per-individual fitness values, but do little else + // In nonWF models we are required to fill in the per-individual fitness values, but do little else. individual_cached_fitness_OVERRIDE_ = false; +#if DEBUG_TRAIT_DEMAND() + std::cout << "# " << community_.Tick() << " --- UpdateFitness() determined a constant fitness of " << constant_fitness_effects << "; setting cached_fitness_UNSAFE_ for all individuals in subpop p" << subpopulation_id_ << std::endl; +#endif + for (slim_popsize_t individual_index = 0; individual_index < parent_subpop_size_; individual_index++) { Individual *ind = parent_individuals_[individual_index]; @@ -1384,48 +1470,50 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio ind->cached_unscaled_fitness_ = (slim_fitness_t)constant_unscaled_fitness_value; #endif - ind->cached_fitness_UNSAFE_ = (slim_fitness_t)constant_fitness_value; + ind->cached_fitness_UNSAFE_ = (slim_fitness_t)constant_fitness_effects; } } } else { - // we cannot override individual cached fitness values; individuals are not all neutral fitness + // We cannot override individual cached fitness values; individuals are not all constant fitness. individual_cached_fitness_OVERRIDE_ = false; - // demand phenotypes for all the relevant traits - if (p_direct_effect_trait_indices.size()) + // Demand phenotypes for all the relevant traits. + if (trait_indices_count > 0) { #if DEBUG_TRAIT_DEMAND() - std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding traits {"; - for (slim_trait_index_t trait_index : p_direct_effect_trait_indices) - std::cout << " " << species_.Traits()[trait_index]->Name(); - std::cout << " } in subpop p" << subpopulation_id_ << ", forceRecalc == " << (p_force_trait_recalculation ? "T" : "F") << std::endl; + std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding traits {"; + for (slim_trait_index_t trait_index : direct_effect_trait_indices) + std::cout << " " << species_.Traits()[trait_index]->Name(); + std::cout << " } in subpop p" << subpopulation_id_ << ", forceRecalc == " << (p_force_trait_recalculation ? "T" : "F") << std::endl; #endif if (p_force_trait_recalculation) - Individual_Class::DemandPhenotype_SUBPOP(&species_, this, p_direct_effect_trait_indices, p_subpop_mutationEffect_callbacks); + Individual_Class::DemandPhenotype_SUBPOP(&species_, this, direct_effect_trait_indices, p_subpop_mutationEffect_callbacks); else - Individual_Class::DemandPhenotype_SUBPOP(&species_, this, p_direct_effect_trait_indices, p_subpop_mutationEffect_callbacks); + Individual_Class::DemandPhenotype_SUBPOP(&species_, this, direct_effect_trait_indices, p_subpop_mutationEffect_callbacks); } else { + // Note that we can have demand for zero traits and still have work to do here, because of + // fitnessEffect() callbacks and subpopulation fitnessScaling and constant traits #if DEBUG_TRAIT_DEMAND() - std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding NO traits in subpop p" << subpopulation_id_ << std::endl; + std::cout << "# " << community_.Tick() << " --- UpdateFitness() demanding NO traits in subpop p" << subpopulation_id_ << std::endl; #endif } - // then loop over individuals and pull together the relevant phenotype values, fitnessEffect() callbacks, - // subpopulation fitnessScaling, and individual fitnessScaling to produce final individual fitness values; - // we choose our _CalculateFitnessAfterDemand() template based upon flags and execute it to calculate fitness values - bool f_has_subpop_fitnessScaling = (subpop_fitness_scaling != (slim_fitness_t)1.0); + // Then loop over individuals and pull together the relevant phenotype values, fitnessEffect() callbacks, + // subpopulation fitnessScaling, and individual fitnessScaling to produce final individual fitness values. + // We choose a _CalculateFitnessAfterDemand() template here, then execute it to calculate fitness values. + bool f_has_constant_effects = (constant_fitness_effects != (slim_fitness_t)1.0); bool f_has_ind_fitnessScaling = Individual::s_any_individual_fitness_scaling_set_; bool f_has_fitnessEffect_callbacks = (p_subpop_fitnessEffect_callbacks.size() > 0); - bool f_has_trait_direct_effects = (p_direct_effect_trait_indices.size() > 0); - bool f_single_trait = (p_direct_effect_trait_indices.size() == 1); - void (Subpopulation::*_CalculateFitnessAfterDemand_TEMPLATED)(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) = nullptr; + bool f_has_trait_direct_effects = (trait_indices_count > 0); + bool f_single_trait = (trait_indices_count == 1); + void (Subpopulation::*_CalculateFitnessAfterDemand_TEMPLATED)(const std::vector &, const std::vector &, slim_fitness_t) = nullptr; - if (f_has_subpop_fitnessScaling) + if (f_has_constant_effects) { if (f_has_ind_fitnessScaling) { if (f_has_fitnessEffect_callbacks) { @@ -1480,22 +1568,21 @@ void Subpopulation::UpdateFitness(std::vector &p_subpop_mutatio } } - (this->*(_CalculateFitnessAfterDemand_TEMPLATED))(p_subpop_fitnessEffect_callbacks, p_direct_effect_trait_indices); + (this->*(_CalculateFitnessAfterDemand_TEMPLATED))(p_subpop_fitnessEffect_callbacks, direct_effect_trait_indices, constant_fitness_effects); } if (model_type_ == SLiMModelType::kModelTypeWF) UpdateWFFitnessBuffers(); } -template -void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices) +template +void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &p_fitnessEffect_callbacks, const std::vector &p_direct_effect_trait_indices, slim_fitness_t p_constant_effects) { // manage the shuffle buffer; this is not quite as fast as templatizing this flag, but it's simpler, and it // only adds overhead when fitnessEffect() callbacks are present, otherwise it get optimized out completely const bool f_has_shuffle_buffer = (f_has_fitnessEffect_callbacks && species_.RandomizingCallbackOrder()); slim_popsize_t *shuffle_buf = (f_has_shuffle_buffer ? species_.BorrowShuffleBuffer(parent_subpop_size_) : nullptr); - slim_fitness_t subpop_fitness_scaling = (f_has_subpop_fitnessScaling ? (slim_fitness_t)subpop_fitness_scaling_ : (slim_fitness_t)0.0); // guaranteed >= 0.0 slim_trait_index_t single_trait_index = (f_has_trait_effects && f_single_trait ? p_direct_effect_trait_indices[0] : 0); for (slim_popsize_t shuffle_index = 0; shuffle_index < parent_subpop_size_; shuffle_index++) @@ -1527,22 +1614,26 @@ void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p { // fitness is > 0.0, so continue calculating + // Note that we do not need to call fitnessEffect() callbacks in other branches; we explicitly + // document that they are not necessarily called if the fitness is already determined to be 0.0. if (f_has_fitnessEffect_callbacks) fitness *= (slim_fitness_t)ApplyFitnessEffectCallbacks(p_fitnessEffect_callbacks, ind); // guaranteed >= 0.0 #ifdef SLIMGUI + // Note that SLiMgui's unscaled fitness now excludes all constant fitness effects passed in, + // not just the subpopulation fitnessScaling value. This is a very minor policy change. ind->cached_unscaled_fitness_ = fitness; #endif - if (f_has_subpop_fitnessScaling) - fitness *= subpop_fitness_scaling; + if (f_has_constant_effects) + fitness *= p_constant_effects; ind->cached_fitness_UNSAFE_ = fitness; } else { // with additive traits, fitness could be < 0.0 (and gets clipped to 0.0); otherwise it is 0.0 - // we're already fitness 0.0, so we can skip multiplying in subpop_fitness_scaling + // we're already fitness 0.0, so we can skip p_constant_effects and fitnessEffect() callbacks #ifdef SLIMGUI ind->cached_unscaled_fitness_ = 0.0; #endif @@ -1563,30 +1654,30 @@ void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p species_.ReturnShuffleBuffer(); } -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); -template void Subpopulation::_CalculateFitnessAfterDemand(std::vector &p_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); +template void Subpopulation::_CalculateFitnessAfterDemand(const std::vector &, const std::vector &, slim_fitness_t); // WF only: void Subpopulation::UpdateWFFitnessBuffers(void) @@ -1959,7 +2050,7 @@ slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutati return p_effect; } -slim_fitness_t Subpopulation::ApplyFitnessEffectCallbacks(std::vector &p_fitnessEffect_callbacks, Individual *p_individual) +slim_fitness_t Subpopulation::ApplyFitnessEffectCallbacks(const std::vector &p_fitnessEffect_callbacks, Individual *p_individual) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::ApplyFitnessEffectCallbacks(): running Eidos callback"); @@ -4571,7 +4662,7 @@ template bool Subpopulation::MungeIndividualCloned_1CH_H(Indi template bool Subpopulation::MungeIndividualCloned_1CH_H(Individual *, slim_pedigreeid_t, Individual *); // nonWF only: -void Subpopulation::ApplyReproductionCallbacks(std::vector &p_reproduction_callbacks, slim_popsize_t p_individual_index) +void Subpopulation::ApplyReproductionCallbacks(const std::vector &p_reproduction_callbacks, slim_popsize_t p_individual_index) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::ApplyReproductionCallbacks(): running Eidos callback"); @@ -4786,7 +4877,7 @@ void Subpopulation::MergeReproductionOffspring(void) } // nonWF only: -bool Subpopulation::ApplySurvivalCallbacks(std::vector &p_survival_callbacks, Individual *p_individual, slim_fitness_t p_fitness, double p_draw, bool p_surviving) +bool Subpopulation::ApplySurvivalCallbacks(const std::vector &p_survival_callbacks, Individual *p_individual, slim_fitness_t p_fitness, double p_draw, bool p_surviving) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::ApplySurvivalCallbacks(): running Eidos callback"); @@ -4942,7 +5033,7 @@ bool Subpopulation::ApplySurvivalCallbacks(std::vector &p_survi return p_surviving; } -void Subpopulation::ViabilitySurvival(std::vector &p_survival_callbacks) +void Subpopulation::ViabilitySurvival(const std::vector &p_survival_callbacks) { THREAD_SAFETY_IN_ANY_PARALLEL("Subpopulation::ViabilitySurvival(): usage of statics, probably many other issues"); diff --git a/core/subpopulation.h b/core/subpopulation.h index f4ac0cda..070af670 100644 --- a/core/subpopulation.h +++ b/core/subpopulation.h @@ -380,18 +380,18 @@ class Subpopulation : public EidosDictionaryUnretained void CheckIndividualIntegrity(void); // this is called by Population::RecalculateFitness(); first it expresses demand for traits that have direct fitness effects, then it recalculates fitness values - void UpdateFitness(std::vector &p_subpop_mutationEffect_callbacks, std::vector &p_subpop_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation); + void UpdateFitness(const std::vector &p_subpop_mutationEffect_callbacks, const std::vector &p_subpop_fitnessEffect_callbacks, const std::vector &p_direct_effect_trait_indices, bool p_force_trait_recalculation); // this is called only by UpdateFitness(), and calculates fitness values given that trait values have already been demanded/calculated - template - void _CalculateFitnessAfterDemand(std::vector &p_subpop_fitnessEffect_callbacks, std::vector &p_direct_effect_trait_indices); + template + void _CalculateFitnessAfterDemand(const std::vector &p_subpop_fitnessEffect_callbacks, const std::vector &p_direct_effect_trait_indices, double p_constant_effects); // WF only: this updates the WF model fitness buffers after UpdateFitness() has completed, preparing to draw parents according to relative fitness void UpdateWFFitnessBuffers(void); // applying mutationEffect() and fitnessEffect() callbacks during trait/fitness calculation slim_effect_t ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, Trait *p_trait, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual); - slim_fitness_t ApplyFitnessEffectCallbacks(std::vector &p_fitnessEffect_callbacks, Individual *p_individual); + slim_fitness_t ApplyFitnessEffectCallbacks(const std::vector &p_fitnessEffect_callbacks, Individual *p_individual); // generate newly allocated offspring individuals from parent individuals; these methods loop over // chromosomes/haplosomes, and are templated for speed, providing a set of optimized variants @@ -436,11 +436,11 @@ class Subpopulation : public EidosDictionaryUnretained void SwapChildAndParentHaplosomes(void); // switch to the next generation by swapping; the children become the parents // nonWF only: - void ApplyReproductionCallbacks(std::vector &p_reproduction_callbacks, slim_popsize_t p_individual_index); + void ApplyReproductionCallbacks(const std::vector &p_reproduction_callbacks, slim_popsize_t p_individual_index); void ReproduceSubpopulation(void); void MergeReproductionOffspring(void); - bool ApplySurvivalCallbacks(std::vector &p_survival_callbacks, Individual *p_individual, slim_fitness_t p_fitness, double p_draw, bool p_surviving); - void ViabilitySurvival(std::vector &p_survival_callbacks); + bool ApplySurvivalCallbacks(const std::vector &p_survival_callbacks, Individual *p_individual, slim_fitness_t p_fitness, double p_draw, bool p_surviving); + void ViabilitySurvival(const std::vector &p_survival_callbacks); void IncrementIndividualAges(void); static IndividualSex _ValidateHaplosomesAndChooseSex(ChromosomeType p_chromosome_type, bool p_haplosome1_null, bool p_haplosome2_null, EidosValue *p_sex_value, bool p_sex_enabled, const char *p_caller_name); diff --git a/core/trait.cpp b/core/trait.cpp index b1ea5986..11a032ec 100644 --- a/core/trait.cpp +++ b/core/trait.cpp @@ -11,22 +11,22 @@ #include "species.h" -Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect, bool p_baselineAccumulation) : +Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetDistributionMean, double p_individualOffsetDistributionSD, bool p_directFitnessEffect, bool p_baselineAccumulation) : index_(-1), name_(p_name), type_(p_type), logistic_post_(p_logistic_post), - individualOffsetMean_(p_individualOffsetMean), individualOffsetSD_(p_individualOffsetSD), + individualOffsetDistributionMean_(p_individualOffsetDistributionMean), individualOffsetDistributionSD_(p_individualOffsetDistributionSD), directFitnessEffect_(p_directFitnessEffect), baselineAccumulation_(p_baselineAccumulation), community_(p_species.community_), species_(p_species) { // offsets must always be finite if (!std::isfinite(p_baselineOffset)) - EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property baselineOffset requires a finite value (not NAN or INF)." << EidosTerminate(); - if (!std::isfinite(individualOffsetMean_)) - EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property individualOffsetMean requires a finite value (not NAN or INF)." << EidosTerminate(); - if (!std::isfinite(individualOffsetSD_) || (individualOffsetSD_ < 0.0)) - EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) property individualOffsetSD requires a nonnegative finite value (not NAN or INF)." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Trait::Trait): (internal error) p_baselineOffset requires a finite value (not NAN or INF)." << EidosTerminate(); + if (!std::isfinite(individualOffsetDistributionMean_)) + EIDOS_TERMINATION << "ERROR (Trait::Trait): (internal error) individualOffsetDistributionMean_ requires a finite value (not NAN or INF)." << EidosTerminate(); + if (!std::isfinite(individualOffsetDistributionSD_) || (individualOffsetDistributionSD_ < 0.0)) + EIDOS_TERMINATION << "ERROR (Trait::Trait): (internal error) individualOffsetDistributionSD_ requires a nonnegative finite value (not NAN or INF)." << EidosTerminate(); if (p_logistic_post && (type_ != TraitType::kAdditive)) - EIDOS_TERMINATION << "ERROR (Trait::SetProperty): (internal error) logistic post-transformation is only supported for additive traits." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Trait::Trait): (internal error) p_logistic_post is only supported for additive traits." << EidosTerminate(); // effects for multiplicative traits clip at 0.0 if ((type_ == TraitType::kMultiplicative) && (p_baselineOffset < (slim_trait_offset_t)0.0)) @@ -40,25 +40,25 @@ Trait::Trait(Species &p_species, const std::string &p_name, TraitType p_type, bo void Trait::_RecacheIndividualOffsetDistribution(void) { // cache for the fast case of an individual-offset SD of 0.0 - if (individualOffsetSD_ == 0.0) + if (individualOffsetDistributionSD_ == 0.0) { - individualOffsetFixed_ = true; + individualOffsetDistributionFixed_ = true; if (type_ == TraitType::kMultiplicative) { // multiplicative traits use an exp() transformation to get a lognormal distribution // (effects for multiplicative traits also clip at 0.0, but exp() guarantees that anyway) - individualOffsetFixedValue_ = static_cast(std::exp(individualOffsetMean_)); + individualOffsetDistributionFixedValue_ = static_cast(std::exp(individualOffsetDistributionMean_)); } else { // additive and logistic traits use a normal distribution, so the mean is the mean - individualOffsetFixedValue_ = static_cast(individualOffsetMean_); + individualOffsetDistributionFixedValue_ = static_cast(individualOffsetDistributionMean_); } } else { - individualOffsetFixed_ = false; + individualOffsetDistributionFixed_ = false; } } @@ -97,14 +97,14 @@ slim_trait_offset_t Trait::_DrawIndividualOffset(void) const { // multiplicative traits use an exp() transformation to get a lognormal distribution // (effects for multiplicative traits also clip at 0.0, but exp() guarantees that anyway) - double normal_draw = gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_; + double normal_draw = gsl_ran_gaussian(rng, individualOffsetDistributionSD_) + individualOffsetDistributionMean_; return static_cast(std::exp(normal_draw)); } else { // additive and logistic traits use a normal distribution, so the mean is the mean - double normal_draw = gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_; + double normal_draw = gsl_ran_gaussian(rng, individualOffsetDistributionSD_) + individualOffsetDistributionMean_; return static_cast(normal_draw); } @@ -169,11 +169,11 @@ EidosValue_SP Trait::GetProperty(EidosGlobalStringID p_property_id) } case gID_individualOffsetMean: { - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(individualOffsetMean_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(individualOffsetDistributionMean_)); } case gID_individualOffsetSD: { - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(individualOffsetSD_)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(individualOffsetDistributionSD_)); } case gID_tag: { @@ -218,8 +218,9 @@ void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_v if (!std::isfinite(value)) EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property individualOffsetMean requires a finite value (not NAN or INF)." << EidosTerminate(); - individualOffsetMean_ = value; + individualOffsetDistributionMean_ = value; _RecacheIndividualOffsetDistribution(); + IndividualOffsetChanged(); return; } case gID_individualOffsetSD: @@ -229,8 +230,9 @@ void Trait::SetProperty(EidosGlobalStringID p_property_id, const EidosValue &p_v if (!std::isfinite(value) || (value < 0.0)) EIDOS_TERMINATION << "ERROR (Trait::SetProperty): property individualOffsetSD requires a nonnegative finite value (not NAN or INF)." << EidosTerminate(); - individualOffsetSD_ = value; + individualOffsetDistributionSD_ = value; _RecacheIndividualOffsetDistribution(); + IndividualOffsetChanged(); return; } case gID_tag: diff --git a/core/trait.h b/core/trait.h index 24f4bec8..65b0e436 100644 --- a/core/trait.h +++ b/core/trait.h @@ -66,13 +66,17 @@ class Trait : public EidosDictionaryRetained slim_trait_offset_t baselineOffset_; // default individual offset distribution parameters, used to generate per-individual offsets - double individualOffsetMean_; - double individualOffsetSD_; + double individualOffsetDistributionMean_; + double individualOffsetDistributionSD_; // an optimization for the individual offset distribution, caching a fixed offset value if individualOffsetSD_ // is 0.0; note that the cached fixed value here includes the exp() transform for multiplicative traits - bool individualOffsetFixed_; // true if individualOffsetSD_ == 0.0 - slim_trait_offset_t individualOffsetFixedValue_; // pre-calculated and pre-cast for speed + bool individualOffsetDistributionFixed_; // true if individualOffsetSD_ == 0.0 + slim_trait_offset_t individualOffsetDistributionFixedValue_; // pre-calculated and pre-cast for speed + + // this flag tracks whether every individual has an offset drawn from the trait's current offset distribution; + // it starts false, and is set true if the script overrides an offset, or if the offset distribution changes + bool individualOffsetEverOverridden_ = false; // if true, the calculated trait value is used directly as a fitness effect, automatically // this mimics the previous behavior of SLiM, for multiplicative traits @@ -104,7 +108,7 @@ class Trait : public EidosDictionaryRetained // because trait_all_neutral_mutations_ is set and the trait cannot be influenced by any callbacks in the // current subpopulation / tick, or because an active callback actually sets this trait to be neutral in // this subpopulation / tick. Traits for which this flag is set can be safely elided from trait calculations - // except for the baseline offset and individual offset. + // except for their baseline offset and individual offsets. mutable bool is_pure_neutral_now_; // If set, this flag indicates that the trait currently exhibits independent dominance for all mutations, @@ -115,7 +119,7 @@ class Trait : public EidosDictionaryRetained // based upon the mutations in the non-neutral cache, and even mutations made globally neutral for a given // trait may be kept in the non-neutral cache for other reasons, such as effects on other traits.) Traits // for which this flag is set can be calculated more efficiently, with cached per-mutation-run values. We - // avoid setting this flag to true when is_pure_neutral_now_ is true; being pure neutral takes precedence. + // avoid setting this flag to true when is_pure_neutral_now_ is true; being pure-neutral takes precedence. mutable bool is_pure_independent_dominance_now_; // If set, subject_to_mutationEffect_callback_ indicates that the trait is influenced by a mutationEffect @@ -123,14 +127,16 @@ class Trait : public EidosDictionaryRetained // effect of mutations on that trait cannot be consulted reliably; the callback needs to be considered. mutable bool subject_to_mutationEffect_callback_; - mutable bool subject_to_non_neutral_callback_; + // If a trait is subject to a nonneutral callback, it needs to be evaluated even if a later constant-effect + // callback overrides the nonneutral callback, because the nonneutral callback might have side effects. + mutable bool subject_to_non_global_neutral_callback_; Trait(const Trait&) = delete; // no copying Trait& operator=(const Trait&) = delete; // no copying Trait(void) = delete; // no null constructor - explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetMean, double p_individualOffsetSD, bool p_directFitnessEffect, bool p_baselineAccumulation); + explicit Trait(Species &p_species, const std::string &p_name, TraitType p_type, bool p_logistic_post, slim_trait_offset_t p_baselineOffset, double p_individualOffsetDistributionMean, double p_individualOffsetDistributionSD, bool p_directFitnessEffect, bool p_baselineAccumulation); ~Trait(void); inline __attribute__((always_inline)) slim_trait_index_t Index(void) const { return index_; } @@ -143,9 +149,13 @@ class Trait : public EidosDictionaryRetained inline __attribute__((always_inline)) slim_trait_offset_t BaselineOffset(void) const { return baselineOffset_; }; inline __attribute__((always_inline)) void SetBaselineOffset(slim_trait_offset_t p_baseline) { baselineOffset_ = p_baseline; }; - void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetFixed_ and individualOffsetFixedValue_ - slim_trait_offset_t _DrawIndividualOffset(void) const; // draws from the distribution defined by individualOffsetMean_ and individualOffsetSD_ - inline __attribute__((always_inline)) slim_trait_offset_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } + void _RecacheIndividualOffsetDistribution(void); // caches individualOffsetDistributionFixed_ and individualOffsetDistributionFixedValue_ + slim_trait_offset_t _DrawIndividualOffset(void) const; // draws from the distribution defined by individualOffsetDistributionMean_ and individualOffsetDistributionSD_ + inline __attribute__((always_inline)) slim_trait_offset_t DrawIndividualOffset(void) const { return (individualOffsetDistributionFixed_) ? individualOffsetDistributionFixedValue_ : _DrawIndividualOffset(); } + //inline __attribute__((always_inline)) slim_trait_offset_t IndividualOffsetDistributionMean(void) const { return individualOffsetDistributionMean_; } // a bit dangerous because of the exp() post-transform; probably nobody should use this + inline __attribute__((always_inline)) slim_trait_offset_t IndividualOffsetDistributionSD(void) const { return individualOffsetDistributionSD_; } + inline __attribute__((always_inline)) void IndividualOffsetChanged(void) { individualOffsetEverOverridden_ = true; } + inline __attribute__((always_inline)) bool IndividualOffsetEverChanged(void) { return individualOffsetEverOverridden_; } inline __attribute__((always_inline)) bool HasDirectFitnessEffect(void) const { return directFitnessEffect_; } inline __attribute__((always_inline)) bool HasBaselineAccumulation(void) const { return baselineAccumulation_; } From 2abd3323b3443cef542fa8cc515395a30de5cc3f Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 1 Feb 2026 11:24:29 -0600 Subject: [PATCH 105/107] make Eidos_IsClose() function for internal use --- eidos/eidos_functions_values.cpp | 56 +++----------------------------- eidos/eidos_globals.cpp | 25 -------------- eidos/eidos_globals.h | 30 +++++++++++++++-- 3 files changed, 33 insertions(+), 78 deletions(-) diff --git a/eidos/eidos_functions_values.cpp b/eidos/eidos_functions_values.cpp index c63e59be..0ba9ff6f 100644 --- a/eidos/eidos_functions_values.cpp +++ b/eidos/eidos_functions_values.cpp @@ -1176,24 +1176,10 @@ EidosValue_SP Eidos_ExecuteFunction_allClose(const std::vector &p { double xv = (x_count == 1) ? xv_singleton : x_data[value_index]; double yv = (y_count == 1) ? yv_singleton : y_data[value_index]; + bool close = Eidos_IsClose(xv, yv, rtol, atol, equalNAN); - if (std::isfinite(xv) && std::isfinite(yv)) - { - if (std::abs(xv - yv) <= atol + rtol * std::abs(yv)) - continue; - } - else if (std::isinf(xv) && std::isinf(yv)) - { - if (std::signbit(xv) == std::signbit(yv)) - continue; - } - else if (std::isnan(xv) && std::isnan(yv)) - { - if (equalNAN) - continue; - } - - return gStaticEidosValue_LogicalF; + if (!close) + return gStaticEidosValue_LogicalF; } } else @@ -1799,30 +1785,7 @@ EidosValue_SP Eidos_ExecuteFunction_isClose(const std::vector &p_ double xv = x_value->FloatAtIndex_NOCAST(0, nullptr); double yv = y_value->FloatAtIndex_NOCAST(0, nullptr); - if (std::isfinite(xv) && std::isfinite(yv)) - { - // if xv and yv are finite, they are "close" if absolute(xv - yv) <= (atol + rtol * absolute(yv)) - // note that this mirrors the behavior of the numpy function isclose(), which this is based upon; - // it is documented at https://numpy.org/doc/stable/reference/generated/numpy.isclose.html. - // Note that Python's built-in math.isclose() has a different criterion, and different defaults; - // see https://docs.python.org/3/library/math.html#math.isclose. - bool close = (std::abs(xv - yv) <= atol + rtol * std::abs(yv)); - - return (close ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); - } - else - { - // if xv and yv are infinite, they are "close" if and only if they have the same sign - if (std::isinf(xv) && std::isinf(yv)) - return ((std::signbit(xv) == std::signbit(yv)) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); - - // if xv and yv are NAN, they are "close" if and only if the equalNAN flag is true - if (std::isnan(xv) && std::isnan(yv)) - return (equalNAN ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); - - // all other cases involving INF and/or NAN are not "close" - return gStaticEidosValue_LogicalF; - } + return (Eidos_IsClose(xv, yv, rtol, atol, equalNAN) ? gStaticEidosValue_LogicalT : gStaticEidosValue_LogicalF); } else { @@ -1838,16 +1801,7 @@ EidosValue_SP Eidos_ExecuteFunction_isClose(const std::vector &p_ { double xv = (x_count == 1) ? xv_singleton : x_data[value_index]; double yv = (y_count == 1) ? yv_singleton : y_data[value_index]; - bool close; - - if (std::isfinite(xv) && std::isfinite(yv)) - close = (std::abs(xv - yv) <= atol + rtol * std::abs(yv)); - else if (std::isinf(xv) && std::isinf(yv)) - close = (std::signbit(xv) == std::signbit(yv)); - else if (std::isnan(xv) && std::isnan(yv)) - close = equalNAN; - else - close = false; + bool close = Eidos_IsClose(xv, yv, rtol, atol, equalNAN); logical_result->set_logical_no_check(close, value_index); } diff --git a/eidos/eidos_globals.cpp b/eidos/eidos_globals.cpp index 77da8390..21441281 100644 --- a/eidos/eidos_globals.cpp +++ b/eidos/eidos_globals.cpp @@ -3428,31 +3428,6 @@ double Eidos_ExactSum(const double *p_double_vec, int64_t p_vec_length) return hi; } -bool Eidos_ApproximatelyEqual(double a, double b) -{ - // different signs is a mismatch - if (std::signbit(a) != std::signbit(b)) - return false; - - // both zero is not a mismatch (getting rid of this case for div-by-zero safety - if ((a == 0) && (b == 0)) - return true; - - // one zero (and one not) is a mismatch - if ((a == 0) || (b == 0)) - return false; - - // one significantly bigger is a mismatch - if (a / b > 1.0001) - return false; - - // the other significantly bigger is a mismatch - if (b / a > 1.0001) - return false; - - return true; -} - std::vector Eidos_string_split(const std::string &joined_string, const std::string &separator) { std::vector tokens; diff --git a/eidos/eidos_globals.h b/eidos/eidos_globals.h index 3db32270..d91295a1 100644 --- a/eidos/eidos_globals.h +++ b/eidos/eidos_globals.h @@ -807,8 +807,34 @@ double Eidos_TTest_OneSample(const double *p_set1, int p_count1, double p_mu, do // Exact summation of a floating-point vector using the Shewchuk algorithm; surprisingly, not in the GSL double Eidos_ExactSum(const double *p_double_vec, int64_t p_vec_length); -// Approximate equality of two floating-point numbers, within a ratio tolerance of 1.0001 -bool Eidos_ApproximatelyEqual(double a, double b); +// Approximate equality of two floating-point numbers, following the isClose() method's heuristics +inline __attribute__((always_inline)) bool Eidos_IsClose(double xv, double yv, double rtol = 1.0e-05, double atol = 1.0e-08, bool equalNAN = false) +{ + if (std::isfinite(xv) && std::isfinite(yv)) + { + // if xv and yv are finite, they are "close" if absolute(xv - yv) <= (atol + rtol * absolute(yv)) + // note that this mirrors the behavior of the numpy function isclose(), which this is based upon; + // it is documented at https://numpy.org/doc/stable/reference/generated/numpy.isclose.html. + // Note that Python's built-in math.isclose() has a different criterion, and different defaults; + // see https://docs.python.org/3/library/math.html#math.isclose. + bool close = (std::abs(xv - yv) <= atol + rtol * std::abs(yv)); + + return close; + } + else + { + // if xv and yv are infinite, they are "close" if and only if they have the same sign + if (std::isinf(xv) && std::isinf(yv)) + return (std::signbit(xv) == std::signbit(yv)); + + // if xv and yv are NAN, they are "close" if and only if the equalNAN flag is true + if (std::isnan(xv) && std::isnan(yv)) + return equalNAN; + + // all other cases involving INF and/or NAN are not "close" + return false; + } +} // Split a std::string into a vector of substrings separated by a given delimiter std::vector Eidos_string_split(const std::string &p_str, const std::string &p_delim); From 39870e5bad05b477308c2ad7f18cb8a1049aa55d Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 1 Feb 2026 11:43:12 -0600 Subject: [PATCH 106/107] fix an obscure unit test bug --- core/slim_test_genetics.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index 831c7df2..8155c09b 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -1240,15 +1240,16 @@ late() { sim.killIndividuals(p1.subsetIndividuals(minAge=1)); } SLiMAssertScriptRaise(mt_base_p1 + "5 late() { mut = sim.mutations[0:4]; mut.setHemizygousDominanceForTrait(0, c(1,INF,3,INF,5)); }", "non-finite after setHemizygousDominanceForTrait()", __LINE__); // MutationType defaultHemizygousDominanceForTrait() and setDefaultHemizygousDominanceForTrait() + // note that `m1.convertToSubstitution = F` is needed in some cases, because baseline accumulation will raise an error if substitution occurs with a hemizygous dominance coefficient != 1.0 SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultHemizygousDominanceForTrait(0), 1.0)) stop(); } 5 late() { }"); SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultHemizygousDominanceForTrait(1), 1.0)) stop(); } 5 late() { }"); SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { if (!identical(m1.defaultHemizygousDominanceForTrait(c(0,1)), c(1.0, 1.0))) stop(); } 5 late() { }"); - SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(0, 0.5); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(0.5, 1.0))) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(1, 0.5); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(1.0, 0.5))) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(NULL, c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(0.25, 0.5))) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(c(0,1), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(0,1)), c(0.25, 0.5))) stop(); }"); - SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(c(1,0), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(1,0)), c(0.25, 0.5))) stop(); }"); - SLiMAssertScriptStop(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(c(1,0), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(0,1)), c(0.25, 0.5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.convertToSubstitution = F; m1.setDefaultHemizygousDominanceForTrait(0, 0.5); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(0.5, 1.0))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.convertToSubstitution = F; m1.setDefaultHemizygousDominanceForTrait(1, 0.5); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(1.0, 0.5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.convertToSubstitution = F; m1.setDefaultHemizygousDominanceForTrait(NULL, c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(NULL), c(0.25, 0.5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.convertToSubstitution = F; m1.setDefaultHemizygousDominanceForTrait(c(0,1), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(0,1)), c(0.25, 0.5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "initialize() { m1.convertToSubstitution = F; m1.setDefaultHemizygousDominanceForTrait(c(1,0), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(1,0)), c(0.25, 0.5))) stop(); }"); + SLiMAssertScriptStop(mt_base_p1 + "initialize() { m1.convertToSubstitution = F; m1.setDefaultHemizygousDominanceForTrait(c(1,0), c(0.25, 0.5)); } 5 late() { mut = sim.mutations[0]; if (!identical(mut.hemizygousDominanceForTrait(c(0,1)), c(0.25, 0.5))) stop(); }"); // hemizygous dominance cannot be NAN, since independent dominance has no meaning for it SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(0, NAN); }", "non-finite after setDefaultHemizygousDominanceForTrait()", __LINE__); SLiMAssertScriptRaise(mt_base_p1 + "initialize() { m1.setDefaultHemizygousDominanceForTrait(NULL, c(0.25, NAN)); }", "non-finite after setDefaultHemizygousDominanceForTrait()", __LINE__); From d51513f731af9c551a558cc5cd37e75e10d7979a Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sun, 1 Feb 2026 11:44:16 -0600 Subject: [PATCH 107/107] switch to using Eidos_IsClose() for phenotype crosschecks --- core/individual.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/core/individual.cpp b/core/individual.cpp index 296ad74a..f79d5ae6 100644 --- a/core/individual.cpp +++ b/core/individual.cpp @@ -7304,11 +7304,10 @@ void Individual_Class::DemandPhenotype_INDIVIDUALS(Species *species, Individual slim_phenotype_t calculated_phenotype = ind->trait_info_[trait_index].phenotype_; slim_phenotype_t check_phenotype = ind->_CheckPhenotypeForTrait(trait_index); - // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about - // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait - // calculations, so I use a larger threshold here of 1e-4; we'll see how this does in practice. - // The goal is not to check for exact equality, but to find bugs that make calculations incorrect. - if (std::abs(calculated_phenotype - check_phenotype) > (slim_phenotype_t)1e-4) + // Use Eidos_IsClose() to test whether our crosscheck produced a "close" value or not. The + // goal is not to check for exact equality, but to find bugs that make calculations incorrect. + // We use a relatively high absolute tolerance here because some calculations are single-precision. + if (!Eidos_IsClose((double)calculated_phenotype, (double)check_phenotype, /* rtol */ 1.0e-05, /* atol */ 1.0e-5)) EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_INDIVIDUALS): (internal error) phenotype check failed for trait " << species->Traits()[trait_index]->Name() << " (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); } } @@ -7718,11 +7717,10 @@ void Individual_Class::DemandPhenotype_SUBPOP(Species *species, Subpopulation *s slim_phenotype_t calculated_phenotype = ind->trait_info_[trait_index].phenotype_; slim_phenotype_t check_phenotype = ind->_CheckPhenotypeForTrait(trait_index); - // BCH 1/9/2026: for single-precision floats, the smallest representable difference from 1 is about - // 1.192e-7 (machine epsilon), or 2^-23, but numerical error will build up over multiple trait - // calculations, so I use a larger threshold here of 1e-4; we'll see how this does in practice. - // The goal is not to check for exact equality, but to find bugs that make calculations incorrect. - if (std::abs(calculated_phenotype - check_phenotype) > (slim_phenotype_t)1e-4) + // Use Eidos_IsClose() to test whether our crosscheck produced a "close" value or not. The + // goal is not to check for exact equality, but to find bugs that make calculations incorrect. + // We use a relatively high absolute tolerance here because some calculations are single-precision. + if (!Eidos_IsClose((double)calculated_phenotype, (double)check_phenotype, /* rtol */ 1.0e-05, /* atol */ 1.0e-5)) EIDOS_TERMINATION << "ERROR (Individual_Class::DemandPhenotype_SUBPOP): (internal error) phenotype check failed for trait " << species->Traits()[trait_index]->Name() << " (calculated_phenotype == " << calculated_phenotype << ", check_phenotype == " << check_phenotype << ", difference == " << (calculated_phenotype - check_phenotype) << ")." << EidosTerminate(); } }