From a494c45b7e3f330959718f018fbe38be01377cb8 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Tue, 1 Jul 2025 18:35:13 +0200 Subject: [PATCH 01/54] 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 02/54] 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 03/54] 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 04/54] 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 05/54] 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 06/54] 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 07/54] 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 08/54] 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 09/54] 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 10/54] 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 11/54] 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 12/54] 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 13/54] 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 14/54] 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 15/54] 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 16/54] 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 17/54] 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 18/54] 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 19/54] 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 20/54] 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 21/54] 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 22/54] 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 23/54] 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 24/54] 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 25/54] 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 26/54] 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 27/54] 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 29/54] 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 30/54] 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 31/54] 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 32/54] 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 33/54] 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 34/54] 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 35/54] 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 36/54] 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 37/54] 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 38/54] 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 39/54] 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 40/54] 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 41/54] 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 42/54] 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 43/54] 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 44/54] 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 45/54] 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 46/54] 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 47/54] 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 48/54] 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 49/54] 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 50/54] 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 51/54] 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 52/54] 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 53/54] 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 54/54] 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);