diff --git a/CMakeLists.txt b/CMakeLists.txt index a712ec8b..61e49642 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -497,7 +497,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. 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/EidosScribe/EidosHelpFunctions.rtf b/EidosScribe/EidosHelpFunctions.rtf index 0ff39d89..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 @@ -1990,10 +1990,9 @@ The \f1\fs18 \cf0 (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 @@ -2009,16 +2008,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 @@ -3691,8 +3689,8 @@ Regarding how values in \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 . 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/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 activeWindow(); for (QWidget *w : topLevels) diff --git a/QtSLiM/QtSLiMChromosomeWidget.cpp b/QtSLiM/QtSLiMChromosomeWidget.cpp index e5f09503..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->dfe_type_ != DFEType::kFixed) || (muttype->dfe_parameters_[0] != 0.0)) + 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/QtSLiM/QtSLiMChromosomeWidget_GL.cpp b/QtSLiM/QtSLiMChromosomeWidget_GL.cpp index 6117ba6e..916c76fe 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! @@ -175,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 = gSLiM_Mutation_Block; slim_chromosome_index_t chromosome_index = chromosome->Index(); for (int registry_index = 0; registry_index < registry_size; ++registry_index) @@ -218,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); + + 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())); @@ -234,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))); @@ -254,6 +261,7 @@ void QtSLiMChromosomeWidget::glDrawMutations(QRect &interiorRect, Chromosome *ch for (auto mutationTypeIter : mut_types) { MutationType *mut_type = mutationTypeIter.second; + EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT if (mut_type->mutation_type_displayed_) { @@ -261,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 ((mut_type->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_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(mut_type->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]; @@ -277,8 +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 - if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mutation->selection_coeff_ == mut_type_selcoeff))) + // 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_effect))) #pragma clang diagnostic pop #pragma GCC diagnostic pop { @@ -309,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) @@ -366,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); + + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -418,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); + + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -486,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! + RGBForEffectSize(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } } @@ -565,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! + 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 5dcfc451..ffdcec04 100644 --- a/QtSLiM/QtSLiMChromosomeWidget_QT.cpp +++ b/QtSLiM/QtSLiMChromosomeWidget_QT.cpp @@ -24,6 +24,8 @@ #include +#include "mutation_block.h" + #include #include #include @@ -171,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 = gSLiM_Mutation_Block; slim_chromosome_index_t chromosome_index = chromosome->Index(); for (int registry_index = 0; registry_index < registry_size; ++registry_index) @@ -214,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); + + 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())); @@ -230,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))); @@ -256,15 +263,16 @@ void QtSLiMChromosomeWidget::qtDrawMutations(QRect &interiorRect, Chromosome *ch if (draw_muttypes_sequentially) { bool mut_type_fixed_color = !mut_type->color_.empty(); + EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT - // 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) + // 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_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : static_cast(mut_type->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]; @@ -273,8 +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 - if ((mutation->mutation_type_ptr_ == mut_type) && (mut_type_fixed_color || (mutation->selection_coeff_ == mut_type_selcoeff))) + // 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_effect))) #pragma clang diagnostic pop #pragma GCC diagnostic pop { @@ -305,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) @@ -362,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); + + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -414,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); + + RGBForEffectSize(static_cast(mut_trait_info[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); SLIM_GL_DEFCOORDS(mutationTickRect); SLIM_GL_PUSHRECT(); @@ -482,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! + RGBForEffectSize(static_cast(substitution->trait_info_[0].effect_size_), &colorRed, &colorGreen, &colorBlue, scalingFactor); } } @@ -561,7 +580,7 @@ void QtSLiMChromosomeWidget::qtDrawFixedSubstitutions(QRect &interiorRect, Chrom } else { - RGBForSelectionCoeff(static_cast(substitution->selection_coeff_), &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/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..3f98d0f3 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,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 = gSLiM_Mutation_Block; + 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) { @@ -492,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()) { @@ -501,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); + RGBForEffectSize(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/QtSLiMScriptTextEdit.cpp b/QtSLiM/QtSLiMScriptTextEdit.cpp index 93b3dcf9..07b3c95e 100644 --- a/QtSLiM/QtSLiMScriptTextEdit.cpp +++ b/QtSLiM/QtSLiMScriptTextEdit.cpp @@ -1573,7 +1573,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 @@ -1591,7 +1591,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_); @@ -2350,6 +2350,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/QtSLiM/QtSLiMTablesDrawer.cpp b/QtSLiM/QtSLiMTablesDrawer.cpp index ba143981..65125786 100644 --- a/QtSLiM/QtSLiMTablesDrawer.cpp +++ b/QtSLiM/QtSLiMTablesDrawer.cpp @@ -81,14 +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; - - sample_size = (mut_type->dfe_type_ == DFEType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast + EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT + + sample_size = (DES_info.DES_type_ == DESType::kScript) ? 100000 : 1000000; // large enough to make curves pretty smooth, small enough to be reasonably fast draws.reserve(sample_size); 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 &DES_info = mutationType->effect_distributions_[0]; // FIXME MULTITRAIT if (p_index.column() == 0) { @@ -552,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(mutationType->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 (mutationType->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 (mutationType->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 < mutationType->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(mutationType->dfe_strings_[paramIndex]); + QString DES_string = QString::fromStdString(DES_info.DES_strings_[paramIndex]); - paramString += ("\"" + dfe_string + "\""); + paramString += ("\"" + DES_string + "\""); - if (paramIndex < mutationType->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 < mutationType->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 (mutationType->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(mutationType->dfe_parameters_[paramIndex], 0, 'f', 3); + paramString += QString("%1=%2").arg(paramSymbol).arg(DES_info.DES_parameters_[paramIndex], 0, 'f', 3); - if (paramIndex < mutationType->dfe_parameters_.size() - 1) + if (paramIndex < DES_info.DES_parameters_.size() - 1) paramString += ", "; } } @@ -664,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(""); } @@ -675,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 16db13d3..6d5f3a3f 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() // @@ -1898,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); } @@ -2114,6 +2130,56 @@ 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); + + // the above autofix is imperfect; if the user is assigning into dominanceCoeff, it really needs to be corrected to be a call to setDefaultDominanceForTrait() + + if (terminationMessage.contains("property hemizygousDominanceCoeff is not defined for object element type MutationType") && + (selectionString == "hemizygousDominanceCoeff")) + return offerAndExecuteAutofix(selection, "defaultHemizygousDominanceForTrait()", "The `hemizygousDominanceCoeff` property of MutationType has become the method `defaultHemizygousDominanceForTrait()`.", terminationMessage); + + // the above autofix is imperfect; if the user is assigning into hemizygousDominanceCoeff, it really needs to be corrected to be a call to setDefaultHemizygousDominanceForTrait() + + if (terminationMessage.contains("property distributionType is not defined for object element type MutationType") && + (selectionString == "distributionType")) + return offerAndExecuteAutofix(selection, "effectDistributionTypeForTrait()", "The `distributionType` property of MutationType has become the method `effectDistributionTypeForTrait()`.", terminationMessage); + + if (terminationMessage.contains("property distributionParams is not defined for object element type MutationType") && + (selectionString == "distributionParams")) + return offerAndExecuteAutofix(selection, "effectDistributionParamsForTrait()", "The `distributionParams` property of MutationType has become the method `effectDistributionParamsForTrait()`.", terminationMessage); + + if ((afterSelection1String == "(") && + terminationMessage.contains("method setDistribution() is not defined on object element type MutationType") && + (selectionPlus1AfterString == "setDistribution(")) + return offerAndExecuteAutofix(selectionPlus1After, "setEffectDistributionForTrait(NULL, ", "The `setDistribution()` method of MutationType has become the method `setEffectDistributionForTrait()`.", terminationMessage); + + 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); + + 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); + + 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; } @@ -4367,6 +4433,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); @@ -4962,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/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/QtSLiM/help/EidosHelpFunctions.html b/QtSLiM/help/EidosHelpFunctions.html index d34bd955..be083587 100644 --- a/QtSLiM/help/EidosHelpFunctions.html +++ b/QtSLiM/help/EidosHelpFunctions.html @@ -179,7 +179,7 @@

(float)rdirichlet(integer$ n, numeric alpha)

Returns a matrix of n random draws from a Dirichlet distribution with vector of shape parameters alpha.  The Dirichlet distribution is a multidimensional generalization of the beta distribution, sometimes called the multivariate beta distribution; see also rbeta().  All values in alpha must be positive and finite, and alpha must be of length >= 2.  The return value is a matrix with n rows and size(alpha) columns, each row containing a single Dirichlet random deviate.

(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])

@@ -298,7 +298,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 8d5837f8..deabd68c 100644 --- a/QtSLiM/help/SLiMHelpCallbacks.html +++ b/QtSLiM/help/SLiMHelpCallbacks.html @@ -5,7 +5,7 @@ - + @@ -184,6 +180,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.

@@ -253,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.

@@ -277,8 +275,8 @@

Before SLiM 4, this method also took a originGeneration parameter.  This was deprecated (the origin generation was then required to be equal to the current generation, for internal consistency), and was removed in SLiM 4.

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

Note that in nonWF models that use tree-sequence recording, mutations cannot be added to an individual after the tick in which the individual is created (i.e., when the age of the individual is greater than 0), to prevent the possibility of inconsistencies in the recorded tree sequence.

-

+ (object<Mutation>)addNewMutation(io<MutationType> mutationType, numeric selectionCoeff, integer position, [Nio<Subpopulation> originSubpop = NULL], [Nis nucleotide = NULL])

-

Add new mutations to the target haplosomes with the specified mutationType (specified by the MutationType object or by integer identifier), selectionCoeff, position, originTick (which may be NULL, the default, to specify the current tick; otherwise, beginning in SLiM 3.5, it must be equal to the current tick anyway, as other uses of this property have been deprecated), and originSubpop (specified by the Subpopulation object or by integer identifier, or by NULL, the default, to specify the subpopulation to which the first target haplosome belongs).  If originSubpop is supplied as an integer, it is intentionally not checked for validity; you may use arbitrary values of originSubpop to “tag” the mutations that you create.  The addNewDrawnMutation() method may be used instead if you wish selection coefficients to be drawn from the mutation types of the mutations.  All of the target haplosomes must be associated with the same Chromosome object, since each new mutation is added to all of the target haplosomes.

+

+ (object<Mutation>)addNewMutation(io<MutationType> mutationType, numeric effect, integer position, [Nio<Subpopulation> originSubpop = NULL], [Nis nucleotide = NULL])

+

Add new mutations to the target haplosomes with the specified mutationType (specified by the MutationType object or by integer identifier), effect, position, originTick (which may be NULL, the default, to specify the current tick; otherwise, beginning in SLiM 3.5, it must be equal to the current tick anyway, as other uses of this property have been deprecated), and originSubpop (specified by the Subpopulation object or by integer identifier, or by NULL, the default, to specify the subpopulation to which the first target haplosome belongs).  If originSubpop is supplied as an integer, it is intentionally not checked for validity; you may use arbitrary values of originSubpop to “tag” the mutations that you create.  The addNewDrawnMutation() method may be used instead if you wish selection coefficients to be drawn from the mutation types of the mutations.  All of the target haplosomes must be associated with the same Chromosome object, since each new mutation is added to all of the target haplosomes.

In non-nucleotide-based models, mutationType will always be a non-nucleotide-based mutation type, and so nucleotide must be NULL (the default).  In a nucleotide-based model, mutationType might still be non-nucleotide-based (in which case nucleotide must still be NULL), or mutationType might be nucleotide-based, in which case a non-NULL value must be supplied for nucleotide, specifying the nucleotide(s) to be associated with the new mutation(s).  Nucleotides may be specified with string values ("A", "C", "G", or "T"), or with integer values (A=0, C=1, G=2, T=3).  If a nucleotide mutation already exists at the mutating position, it is replaced automatically in accordance with the stacking policy for nucleotide-based mutation types.  No check is performed that a new mutation’s nucleotide differs from the ancestral sequence, or that its selection coefficient is consistent with other mutations that may already exist at the given position with the same nucleotide; model consistency is the responsibility of the model.

The new mutations created by this method are returned, even if their actual addition is prevented by the mutation stacking policy (see the mutationStackPolicy property of MutationType).  However, the order of the mutations in the returned vector is not guaranteed to be the same as the order in which the values are specified in parameter vectors, unless the position parameter is specified in ascending order.  In other words, pre-sorting the parameters to this method into ascending order by position, using order() and subsetting, will guarantee that the order of the returned vector of mutations corresponds to the order of elements in the parameters to this method; otherwise, no such guarantee exists.

Beginning in SLiM 2.1, this is a class method, not an instance method.  This means that it does not get multiplexed out to all of the elements of the receiver (which would add a different new mutation to each element); instead, it is performed as a single operation, adding the same new mutation object to all of the elements of the receiver.  Before SLiM 2.1, to add the same mutation to multiple haplosomes, it was necessary to call addNewMutation() on one of the haplosomes, and then add the returned Mutation object to all of the other haplosomes using addMutations().  That is not necessary in SLiM 2.1 and later, because of this change (although doing it the old way does no harm and produces identical behavior).  Pre-2.1 code that actually relied upon the old multiplexing behavior will no longer work correctly (but this is expected to be an extremely rare pattern of usage).

@@ -458,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([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.

– (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.

@@ -468,6 +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([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.

@@ -495,6 +501,13 @@

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([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(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.

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.

@@ -666,10 +679,24 @@

5.10.1  Mutation properties

chromosome => (object<Chromosome>$)

The Chromosome object with which the mutation is associated.

+

dominance => (float)

+

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

+

If the target mutation has been configured to exhibit independent dominance 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.

+

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.

+

hemizygousDominance => (float)

+

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

+

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

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$)

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>$)

@@ -682,22 +709,33 @@

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).

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)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.

-

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.

+

– (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 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.

+

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

+

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

+

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

+

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

+

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

+

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

+

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

5.11  Class MutationType

5.11.1  MutationType properties

color <–> (string$)

@@ -709,25 +747,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 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.

-

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.

-

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.

mutationStackGroup <–> (integer$)

@@ -745,10 +764,28 @@

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([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([Niso<Trait> trait = NULL])

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

– (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, ...)

+

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

title => (string$)

@@ -832,7 +869,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$)

@@ -882,7 +919,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"])

@@ -967,6 +1004,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.

@@ -1013,10 +1052,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])

@@ -1029,7 +1068,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).

@@ -1054,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.

@@ -1083,6 +1123,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.

@@ -1253,7 +1297,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)

@@ -1284,7 +1328,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.  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)

@@ -1311,28 +1356,64 @@

5.18  Class Substitution

5.18.1  Substitution properties

chromosome => (object<Chromosome>$)

-

The Chromosome object with which the mutation is associated.

-

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.

-

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.

+

The Chromosome object with which the substitution is associated.

+

dominance => (float)

+

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

+

effect => (float)

+

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

+

hemizygousDominance => (float)

+

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

+

id => (integer$)

+

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

+

– (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$)

+

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$)

+

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 c2d4f6c5..5be47898 100644 --- a/QtSLiM/help/SLiMHelpFunctions.html +++ b/QtSLiM/help/SLiMHelpFunctions.html @@ -15,9 +15,8 @@ p.p6 {margin: 3.0px 0.0px 3.0px 27.4px; font: 10.0px Optima} p.p7 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.0px Menlo; color: #000000} p.p8 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.0px Menlo} - p.p9 {margin: 3.0px 0.0px 3.0px 27.4px; font: 10.0px Optima; color: #969696} - p.p10 {margin: 0.0px 0.0px 3.0px 54.0px; font: 10.0px Optima; color: #000000} - p.p11 {margin: 3.0px 0.0px 3.0px 27.4px; font: 11.0px Optima; color: #000000} + p.p9 {margin: 0.0px 0.0px 3.0px 54.0px; font: 10.0px Optima; color: #000000} + p.p10 {margin: 3.0px 0.0px 3.0px 27.4px; font: 11.0px Optima; color: #000000} span.s1 {font-kerning: none} span.s2 {font: 9.0px Menlo; font-kerning: none} span.s3 {font: 9.0px Menlo} @@ -26,10 +25,9 @@ span.s6 {font: 10.0px Optima; font-kerning: none} span.s7 {font: 10.0px 'Times New Roman'} span.s8 {color: #000000} - span.s9 {font: 9.0px Menlo; color: #000000} - span.s10 {font: 7.0px 'Apple Color Emoji'} - span.s11 {font: 6.7px Optima; font-kerning: none} - span.s12 {font: 7.3px Optima} + span.s9 {font: 7.0px 'Apple Color Emoji'} + span.s10 {font: 6.7px Optima; font-kerning: none} + span.s11 {font: 7.3px Optima} span.Apple-tab-span {white-space:pre} @@ -101,14 +99,16 @@

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 dominanceCoeff parameter supplies the dominance coefficient for the mutation type; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  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 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.  The global symbol for the new mutation type is immediately available; the return value also provides the new object.

+

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

+

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

+

The dominanceCoeff parameter supplies the default dominance coefficient for the mutation type, for all traits; 0.0 produces no dominance, 1.0 complete dominance, and values greater than 1.0, overdominance.  The default dominance coefficient for the mutation type for a specific trait can subsequently be configured with the setDefaultDominanceForTrait() method if desired.  Note that the mutation type’s default hemizygous dominance coefficient is not supplied to this function; it always defaults to 1.0, but the setDefaultHemizygousDominanceForTrait() method can configure it subsequently if desired.  A dominanceCoeff value of NAN configures the mutation type to use “independent dominance” for new mutations of that type; see the class Trait documentation for discussion of independent dominance.

+

The 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.

@@ -125,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.

@@ -143,8 +142,16 @@

(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.

+

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.

+

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])

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.

@@ -156,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
@@ -199,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).

@@ -225,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])

@@ -267,7 +274,7 @@

The implementation of calcTajimasD(), viewable with functionSource(), treats every mutation as independent in the heterozygosity calculations.  One could regard this choice as embodying an infinite-sites interpretation of the segregating mutations, as with calcHeterozygosity().  Indeed, Tajima’s D can be modified with finite-sites models of π and θ (Misawa and Tajima 1997) though these are not used here.  In most biologically realistic models, such genetic states will be quite rare, and so the impact of this assumption will be negligible; however, in some models this distinction may be important.  See calcPairHeterozygosity() for further discussion.  This function was written by Nick Bailey (currently affiliated with CNRS and the Laboratory of Biometry and Evolutionary Biology at University Lyon 1), with helpful input from Peter Ralph.

(float$)calcVA(object<Individual> individuals, io<MutationType>$ mutType)

Calculates VA, the additive genetic variance, among a vector of individuals (containing at least two elements) passed in individuals, in a particular mutation type mutType that represents quantitative trait loci (QTLs) influencing a quantitative phenotypic trait.  The mutType parameter may be either an integer representing the ID of the desired mutation type, or a MutationType object specified directly.

-

This function assumes that mutations of type mutType encode their effect size upon the quantitative trait in their selectionCoeff property, as is fairly standard in SLiM.  The implementation of calcVA(), which is viewable with functionSource(), is quite simple; if effect sizes are stored elsewhere (such as with setValue()), a new user-defined function following the pattern of calcVA() can easily be written.

+

This function assumes that mutations of type mutType encode their effect size upon the quantitative trait in their effect property, as is fairly standard in SLiM.  The implementation of calcVA(), which is viewable with functionSource(), is quite simple; if effect sizes are stored elsewhere (such as with setValue()), a new user-defined function following the pattern of calcVA() can easily be written.

(float$)calcWattersonsTheta(object<Haplosome> haplosomes, [No<Mutation> muts = NULL], [Ni$ start = NULL], [Ni$ end = NULL])

Calculates Watterson’s theta (a metric of genetic diversity comparable to heterozygosity) for a vector of haplosomes (containing at least one element), based upon the mutations in the haplosomes.  Often haplosomes will be all of the haplosomes in a subpopulation, or in the entire population, but any haplosome vector may be used.  By default, with muts=NULL, the calculation is based upon all mutations in the simulation; the calculation can instead be based upon a subset of mutations, such as mutations of a specific mutation type, by passing the desired vector of mutations for muts.

The calculation can be narrowed to apply to only a window – a subrange of the full chromosome – by passing the interval bounds [start, end] for the desired window.  In this case, the vector of mutations used for the calculation will be subset to include only mutations within the specified window.  The default behavior, with start and end of NULL, provides the haplosome-wide Watterson’s theta.

diff --git a/SLiM.xcodeproj/project.pbxproj b/SLiM.xcodeproj/project.pbxproj index 5aa7c5d7..6ba60f8e 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 */; }; @@ -301,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 */; }; @@ -640,17 +654,13 @@ 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 */; }; - 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 */; }; @@ -1751,6 +1761,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 = ""; }; @@ -1934,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 = ""; }; @@ -1950,6 +1963,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 = ""; }; @@ -1968,6 +1982,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 = ""; }; @@ -1996,7 +2012,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 = ""; }; @@ -2049,11 +2064,12 @@ 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 = ""; }; 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 = ""; }; @@ -2200,8 +2216,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 = ""; }; @@ -2402,8 +2416,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 = ""; @@ -2529,6 +2543,8 @@ 9878A93E1A4E57E70007B9D6 /* species.h */, 9878A93D1A4E57E70007B9D6 /* species.cpp */, 98AC617924BA34ED0001914C /* species_eidos.cpp */, + 98B674232E981AFD00930737 /* trait.h */, + 98B6741D2E981AD400930737 /* trait.cpp */, 98E9A6981A3CD52A000AD4FC /* chromosome.h */, 98E9A6971A3CD52A000AD4FC /* chromosome.cpp */, 98E9A6AD1A3CD5D3000AD4FC /* population.h */, @@ -2541,6 +2557,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 */, @@ -2683,7 +2701,7 @@ isa = PBXGroup; children = ( 9873B12328CE26CD00582D83 /* eidos_openmp.h */, - 987D30EA2EF482AA0096621B /* eidos_simd.h */, + 9860DCB22EEDAADD004B9CC0 /* eidos_simd.h */, 98E9A6B71A3CE35E000AD4FC /* eidos_rng.h */, 98E9A6B61A3CE35E000AD4FC /* eidos_rng.cpp */, 98321F931B406B67007337A3 /* eidos_globals.h */, @@ -2966,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 */, @@ -3717,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 */, @@ -3837,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 */, @@ -3886,6 +3903,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 */, @@ -3925,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 */, @@ -3937,6 +3956,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 */, @@ -4014,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 */, @@ -4063,6 +4082,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 */, @@ -4102,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 */, @@ -4114,6 +4135,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 */, @@ -4291,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 */, @@ -4358,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 */, @@ -4443,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 */, @@ -4464,6 +4486,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 */, @@ -4494,6 +4517,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 */, @@ -4644,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 */, @@ -4711,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 */, @@ -4796,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 */, @@ -4817,6 +4841,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 */, @@ -4847,6 +4872,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 */, @@ -4917,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 */, @@ -5037,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 */, @@ -5086,6 +5111,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 */, @@ -5125,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 */, @@ -5137,6 +5164,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 7d779c4a..ba3e8d46 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"; @@ -511,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! + RGBForEffectSize(substitution->trait_info_[0].effect_size_, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; } } @@ -583,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! + RGBForEffectSize(substitution->trait_info_[0].effect_size_, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; } @@ -639,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 = gSLiM_Mutation_Block; + 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 { @@ -664,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); + + RGBForEffectSize(mut_effect, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; } @@ -679,10 +687,10 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome { // 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 - // had their selection coefficient changed, will be drawn at the end in the usual (slow) way. + // 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 effect changed, will be drawn at the end in the usual (slow) way. float colorRed = 0.0, colorGreen = 0.0, colorBlue = 0.0; int displayPixelWidth = (int)interiorRect.size.width; int16_t *heightBuffer = (int16_t *)malloc(displayPixelWidth * sizeof(int16_t)); @@ -701,20 +709,23 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome if (mut_type->mutation_type_displayed_) { bool mut_type_fixed_color = !mut_type->color_.empty(); + EffectDistributionInfo &DES_info = mut_type->effect_distributions_[0]; // FIXME MULTITRAIT - // 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) + // 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_selcoeff_t mut_type_selcoeff = (mut_type_fixed_color ? 0.0 : (slim_selcoeff_t)mut_type->dfe_parameters_[0]); + slim_effect_t mut_type_effect = (mut_type_fixed_color ? 0.0 : (slim_effect_t)DES_info.DES_parameters_[0]); EIDOS_BZERO(heightBuffer, 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 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_effect))) { if (mutation->chromosome_index_ == chromosome_index) { @@ -744,7 +755,7 @@ - (void)drawMutationsInInteriorRect:(NSRect)interiorRect chromosome:(Chromosome } else { - RGBForSelectionCoeff(mut_type_selcoeff, &colorRed, &colorGreen, &colorBlue, scalingFactor); + RGBForEffectSize(mut_type_effect, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; } @@ -790,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); + RGBForEffectSize(mut_effect, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; NSRectFill(mutationTickRect); } @@ -851,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); + RGBForEffectSize(mut_effect, &colorRed, &colorGreen, &colorBlue, scalingFactor); [[NSColor colorWithCalibratedRed:colorRed green:colorGreen blue:colorBlue alpha:1.0] set]; NSRectFill(mutationTickRect); } @@ -1011,7 +1028,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->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/SLiMgui/CocoaExtra.h b/SLiMgui/CocoaExtra.h index 6a6a0277..243ff966 100644 --- a/SLiMgui/CocoaExtra.h +++ b/SLiMgui/CocoaExtra.h @@ -42,7 +42,7 @@ bool SLiM_AmIBeingDebugged(void); @end 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); // Classes to show a selection index marker when dragging out a selection in a ChromosomeView @interface SLiMSelectionView : NSView @@ -70,7 +70,7 @@ void RGBForSelectionCoeff(double selectionCoeff, float *colorRed, float *colorGr @end -// Classes to show a custom tooltip view displaying a graph of a mutation type's DFE in the muttype table view +// Classes to show a custom tooltip view displaying a graph of a mutation type's DES in the muttype table view // Now also used for similarly displaying an interaction type's IF in the interaction type table view @interface SLiMFunctionGraphToolTipView : NSView @end diff --git a/SLiMgui/CocoaExtra.mm b/SLiMgui/CocoaExtra.mm index 79ec8f3d..3af78fb3 100644 --- a/SLiMgui/CocoaExtra.mm +++ b/SLiMgui/CocoaExtra.mm @@ -115,7 +115,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; @@ -549,14 +549,15 @@ - (void)drawRect:(NSRect)dirtyRect 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 &DES_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 = (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) @@ -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/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/SLiMHelpCallbacks.rtf b/SLiMgui/SLiMHelpCallbacks.rtf index 728492e8..e8350897 100644 --- a/SLiMgui/SLiMHelpCallbacks.rtf +++ b/SLiMgui/SLiMHelpCallbacks.rtf @@ -1,4 +1,4 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2709 +{\rtf1\ansi\ansicpg1252\cocoartf2761 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Optima-Italic;\f1\fnil\fcharset0 Menlo-Italic;\f2\fswiss\fcharset0 Optima-Regular; \f3\fnil\fcharset0 Menlo-Regular;} {\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red28\green0\blue207;\red63\green110\blue116; @@ -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 @@ -1307,7 +1307,7 @@ As with the other callback types, multiple \f0\fs22 callbacks\ \pard\pardeftab397\fi274\ri720\sb40\sa40\partightenfactor0 -\f2\i0 \cf2 SLiM auto-generates new mutations according to the current mutation rate (or rate map) and the genetic structure defined by genomic elements, their genomic element types, the mutation types those genomic element types draw from, and the distribution of fitness effects defined by those mutation types. In nucleotide-based models, the nucleotide sequence and the mutation matrix also play a role in determining both the rate of mutation and the nucleotide mutated to. In some models it can be desirable to modify these dynamics in some way \'96 altering the selection coefficients of new mutations in some way, changing the mutation type used, dictating the nucleotide to be used, replacing the proposed mutation with a pre-existing mutation at the same position, or even suppressing the proposed mutation altogether. To achieve this, one may define a +\f2\i0 \cf2 SLiM auto-generates new mutations according to the current mutation rate (or rate map) and the genetic structure defined by genomic elements, their genomic element types, the mutation types those genomic element types draw from, and the distribution of effect sizes defined by those mutation types. In nucleotide-based models, the nucleotide sequence and the mutation matrix also play a role in determining both the rate of mutation and the nucleotide mutated to. In some models it can be desirable to modify these dynamics in some way \'96 altering the selection coefficients of new mutations in some way, changing the mutation type used, dictating the nucleotide to be used, replacing the proposed mutation with a pre-existing mutation at the same position, or even suppressing the proposed mutation altogether. To achieve this, one may define a \f3\fs18 mutation() \f2\fs22 callback.\ A diff --git a/SLiMgui/SLiMHelpClasses.rtf b/SLiMgui/SLiMHelpClasses.rtf index bbda6076..ae0b547c 100644 --- a/SLiMgui/SLiMHelpClasses.rtf +++ b/SLiMgui/SLiMHelpClasses.rtf @@ -1,8 +1,7 @@ {\rtf1\ansi\ansicpg1252\cocoartf2761 \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 @@ -942,6 +941,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 @@ -1616,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 @@ -1804,7 +1811,7 @@ Beginning in SLiM 2.1, this is a class method, not an instance method. This mea \f4\fs20 ), to prevent the possibility of inconsistencies in the recorded tree sequence.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 +\'a0(object)addNewMutation(io\'a0mutationType, numeric\'a0selectionCoeff, integer\'a0position, [Nio\'a0originSubpop\'a0=\'a0NULL]\cf2 \expnd0\expndtw0\kerning0 +\f3\fs18 \cf0 +\'a0(object)addNewMutation(io\'a0mutationType, numeric\'a0effect, integer\'a0position, [Nio\'a0originSubpop\'a0=\'a0NULL]\cf2 \expnd0\expndtw0\kerning0 , [Nis\'a0nucleotide\'a0=\'a0NULL]\cf0 \kerning1\expnd0\expndtw0 ) \f5 \ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 @@ -1816,7 +1823,7 @@ Beginning in SLiM 2.1, this is a class method, not an instance method. This mea \f4\fs20 object or by \f3\fs18 integer \f4\fs20 identifier), -\f3\fs18 selectionCoeff +\f3\fs18 effect \f4\fs20 , \f3\fs18 position \f4\fs20 , @@ -3641,6 +3648,38 @@ See the description of the \f4\fs20 . This method is provided for speed; it is much faster than the corresponding Eidos code.\ \pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0 +\f3\fs18 \cf2 +\'a0(void)demandPhenotype([Niso\'a0trait\'a0=\'a0NULL], [logical$\'a0forceRecalc\'a0=\'a0F])\ +\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. 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 +\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.\ +\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 @@ -3793,6 +3832,42 @@ 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([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 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. 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 \'96\'a0(float)phenotypeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ +\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 Returns the individual phenotype(s) for the trait(s) specified by +\f3\fs18 trait +\f4\fs20 . The traits can be specified as +\f3\fs18 integer +\f4\fs20 indices or +\f3\fs18 string +\f4\fs20 names of traits in the species, or directly as +\f3\fs18 Trait +\f4\fs20 objects; +\f3\fs18 NULL +\f4\fs20 represents all of the traits in the species, in the order in which they were defined. Phenotypes for a given target individual will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + \f3\fs18 \cf2 +\'a0(void)outputIndividuals([Ns$\'a0filePath\'a0=\'a0NULL], [logical$\'a0append\'a0=\'a0F], [Niso$\'a0chromosome\'a0=\'a0NULL], [logical$\'a0spatialPositions\'a0=\'a0T], [logical$\'a0ages\'a0=\'a0T], [logical$\'a0ancestralNucleotides\'a0=\'a0F], [logical$\'a0pedigreeIDs\'a0=\'a0F], [logical$\'a0objectTags\'a0=\'a0F])\ \pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0 @@ -4151,6 +4226,79 @@ 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([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 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.\ +The parameter +\f3\fs18 offset +\f4\fs20 must follow one of four patterns. In the first pattern, +\f3\fs18 offset +\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 +\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 +\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.\ +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(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 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.\ +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 @@ -5893,6 +6041,98 @@ 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 dominance => (float)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The dominance coefficient(s) of the mutation, taken from the default dominance coefficient(s) of its +\f3\fs18 MutationType +\f4\fs20 . In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 dominanceForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\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.\ +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.\ +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 +\f4\fs20 , +\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.\ +\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 \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 @@ -5912,6 +6152,42 @@ You can get the \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 @@ -5986,42 +6262,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 @@ -6057,45 +6297,212 @@ 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) -\f5 \ +\f3\i0\fs18 \cf2 \'96\'a0(float)dominanceForTrait([Niso\'a0trait\'a0=\'a0NULL])\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 Set the mutation type of the mutation to -\f3\fs18 mutType -\f4\fs20 (which may be specified as either an +\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 identifier or a +\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. Dominance coefficients for a given target mutation will be returned consecutively in the order in which the traits are specified by +\f3\fs18 trait +\f4\fs20 .\ +If the target mutation has been configured to exhibit independent dominance by setting its dominance values to +\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])\ +\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 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. 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(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 +\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 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. 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([Niso\'a0trait\'a0=\'a0NULL], [Nif\'a0dominance\'a0=\'a0NULL])\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\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 +\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.\ +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.\ +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 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 -\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 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])\ \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.\ + +\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 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.\ +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.\ +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 \cf0 \kerning1\expnd0\expndtw0 \'96\'a0(void)setSelectionCoeff(float$\'a0selectionCoeff) -\f5 \ +\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 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.\ +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 -\f4\fs20 \cf0 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 +\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). 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 setDominanceForTrait() +\f4\fs20 methods of \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 -\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.\ +\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\ri720\sb360\sa60\partightenfactor0 \f0\b\fs22 \cf0 5.11 Class MutationType\ @@ -6186,254 +6593,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 \cf0 dominanceCoeff <\'96> (float$)\ -\pard\pardeftab720\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 method -\f3\fs18 recalculateFitness() -\f4\fs20 \'96 but see the documentation of that method for caveats.\ -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. -\f5 \ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - -\f3\fs18 \cf2 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 @@ -6525,47 +6685,217 @@ In nucleotide-based models, the stacking policy for nucleotide-based mutation ty The species to which the target object belongs.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf0 \kerning1\expnd0\expndtw0 tag <\'96> (integer$)\ +\f3\fs18 \cf0 \kerning1\expnd0\expndtw0 tag <\'96> (integer$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf0 A user-defined +\f3\fs18 integer +\f4\fs20 value. The value of +\f3\fs18 tag +\f4\fs20 is initially undefined\cf2 \expnd0\expndtw0\kerning0 +, and it is an error to try to read it\cf0 \kerning1\expnd0\expndtw0 ; 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. See also the +\f3\fs18 getValue() +\f4\fs20 and +\f3\fs18 setValue() +\f4\fs20 methods\cf2 (provided by the +\f3\fs18 Dictionary +\f4\fs20 class; see the Eidos manual)\cf0 , for another way of attaching state to mutation types. +\f5 \ +\pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 + +\f1\i\fs22 \cf0 5.11.2 +\f2\fs18 MutationType +\f1\fs22 methods\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\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 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. The default dominance coefficient is taken by new mutations of this mutation type when they are created, as the value of their +\f3\fs18 dominance +\f4\fs20 property, but that can be changed later with the +\f3\fs18 Mutation +\f4\fs20 method +\f3\fs18 setDominanceForTrait() +\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)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 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. 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([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 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. 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([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 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. The distribution parameters will be of type +\f3\fs18 string +\f4\fs20 for DES type +\f3\fs18 "s" +\f4\fs20 , and type +\f3\fs18 float +\f4\fs20 for all other DES types.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(string)effectDistributionTypeForTrait([Niso\'a0trait\'a0=\'a0NULL])\ +\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 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. 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\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 \'96\'a0(void)setDefaultDominanceForTrait(Niso\'a0trait, float\'a0dominance)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 A user-defined +\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 value. The value of -\f3\fs18 tag -\f4\fs20 is initially undefined\cf2 \expnd0\expndtw0\kerning0 -, and it is an error to try to read it\cf0 \kerning1\expnd0\expndtw0 ; 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. See also the -\f3\fs18 getValue() -\f4\fs20 and -\f3\fs18 setValue() -\f4\fs20 methods\cf2 (provided by the -\f3\fs18 Dictionary -\f4\fs20 class; see the Eidos manual)\cf0 , for another way of attaching state to mutation types. -\f5 \ -\pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 - -\f1\i\fs22 \cf0 5.11.2 -\f2\fs18 MutationType -\f1\fs22 methods\ +\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. The value of +\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).\ +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\i0\fs18 \cf2 \expnd0\expndtw0\kerning0 -\'96\'a0(float)drawSelectionCoefficient([integer$\'a0n\'a0=\'a01])\ +\f3\fs18 \cf2 \'96\'a0(void)setDefaultHemizygousDominanceForTrait(Niso\'a0trait, float\'a0dominance)\ \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 -\f3\fs18 "s" -\f4\fs20 , this method will result in synchronous execution of the DFE\'92s script.\ -\pard\pardeftab397\li720\fi-446\ri720\sb180\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 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. 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 \cf0 \kerning1\expnd0\expndtw0 \'96\'a0(void)setDistribution(string$\'a0distributionType, ...) -\f5 \ +\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 fitness effects for a mutation type. The +\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 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.\ +The \f3\fs18 distributionType \f4\fs20 may be \f3\fs18 "f" @@ -6607,7 +6937,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\ @@ -8940,6 +9272,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 @@ -10147,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 @@ -10186,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() @@ -10661,6 +11024,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 @@ -13458,7 +13849,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 @@ -13466,14 +13857,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 . 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) @@ -13780,41 +14183,99 @@ 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 \cf0 id => (integer$)\ +\f3\fs18 \cf2 dominance => (float)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The dominance coefficient(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 dominanceForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\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 \cf2 effect => (float)\ \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 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 +\f4\fs20 , for example, then \f3\fs18 Substitution -\f4\fs20 object when the mutation fixes. -\f5 \ +\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 fixationTick => (integer$)\ +\f3\fs18 \cf2 hemizygousDominance => (float)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The tick in which this mutation fixed. -\f5 \ +\f4\fs20 \cf2 The hemizygous dominance coefficient(s) of the substitution, carried over from the original mutation object. In a multi-trait model, this property provides the hemizygous dominance coefficients for all of the traits (in the order in which the traits were defined). For more control, see the +\f3\fs18 hemizygousDominanceForTrait() +\f4\fs20 method. Also note that dynamic properties are defined for each trait in the model; if there is a trait named +\f3\fs18 height +\f4\fs20 , for example, then +\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 mutationType => (object$)\ +\f3\fs18 \cf2 id => (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 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.\ \pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 -\f3\fs18 \cf2 \expnd0\expndtw0\kerning0 -nucleotide <\'96> (string$)\ +\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 \cf2 A +\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 \cf2 fixationTick => (integer$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\f4\fs20 \cf2 The tick in which this substitution fixed.\ +\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 + +\f3\fs18 \cf2 mutationType => (object$)\ +\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 + +\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" @@ -13822,15 +14283,15 @@ nucleotide <\'96> (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 <\'96> (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 @@ -13838,35 +14299,25 @@ nucleotide <\'96> (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.\ -\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0 - -\f3\fs18 \cf0 \kerning1\expnd0\expndtw0 originTick => (integer$)\ -\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 - -\f4\fs20 \cf0 The tick in which this mutation arose. -\f5 \ +\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 position => (integer$)\ +\f3\fs18 \cf2 originTick => (integer$)\ \pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0 -\f4\fs20 \cf0 The position in the chromosome of this mutation. -\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 selectionCoeff => (float$)\ +\f3\fs18 \cf2 position => (integer$)\ \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 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 @@ -13876,7 +14327,7 @@ nucleotide <\'96> (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$)\ @@ -13896,7 +14347,173 @@ 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([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 +\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 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. 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([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 +\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 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. 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([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 +\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 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. 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\ +\pard\pardeftab720\ri720\sb120\sa60\partightenfactor0 + +\f1\i\b0 \cf2 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.\ +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 => (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 \cf2 5.19.2 +\f2\fs18 Trait +\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 da05ea00..9ba9e8a1 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 @@ -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,13 +862,15 @@ 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.\ \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 @@ -882,15 +884,37 @@ This function is written in Eidos, and its source code can be viewed with \f1\fs18 string \f2\fs20 giving the name of the new mutation type (such as \f1\fs18 "m5" -\f2\fs20 to specify an ID of 5). The dominanceCoeff parameter supplies the dominance coefficient for the mutation type; +\f2\fs20 to specify an ID of 5). The global symbol for the new mutation type, such as +\f1\fs18 m5 +\f2\fs20 , is immediately available; the return value also provides the new object.\ +The +\f1\fs18 dominanceCoeff +\f2\fs20 parameter supplies the default dominance coefficient for the mutation type, for all traits; \f1\fs18 0.0 \f2\fs20 produces no dominance, \f1\fs18 1.0 \f2\fs20 complete dominance, and values greater than \f1\fs18 1.0 -\f2\fs20 , overdominance. 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 the +\f1\fs18 setDefaultHemizygousDominanceForTrait() +\f2\fs20 method can configure it subsequently if desired. A +\f1\fs18 dominanceCoeff +\f2\fs20 value of +\f1\fs18 NAN +\f2\fs20 configures the mutation type to use \'93independent dominance\'94 for new mutations of that type; see the class +\f1\fs18 Trait +\f2\fs20 documentation for discussion of independent dominance.\ +The \f1\fs18 distributionType -\f2\fs20 may be +\f2\fs20 and the ellipsis parameters together define the distribution of effect size (DES) for the mutation type, for all traits. The DES for the mutation type for a specific trait can subsequently be separately configured with the +\f1\fs18 setEffectDistributionForTrait() +\f2\fs20 method if desired. The +\f1\fs18 distributionType +\f2\fs20 parameter may be \f1\fs18 "f" \f2\fs20 , in which case the ellipsis \f1\fs18 ... @@ -930,7 +954,19 @@ This function is written in Eidos, and its source code can be viewed with \f1\fs18 "s" \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.\ +\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 Note that by default in WF models, all mutations of a given mutation type will be converted into @@ -954,7 +990,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 @@ -1156,22 +1193,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) @@ -1447,11 +1468,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 @@ -1473,6 +1494,126 @@ 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.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 +\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 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 , +\f1\fs18 Species +\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 height +\f2\fs20 , getting and setting an individual\'92s trait value would be possible through the property +\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.height +\f2\fs20 would provide the +\f1\fs18 Trait +\f2\fs20 object named +\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("height", height) +\f2\fs20 would allow the +\f1\fs18 Trait +\f2\fs20 object to be referenced simply as +\f1\fs18 height +\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 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 +\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. 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 +\f1\fs18 individualOffsetSD +\f2\fs20 parameters together define a normal distribution from which individual offsets are drawn to provide what is often called \'93environmental variance\'94 or \'93developmental noise\'94. As for the baseline offset, the individual offset mean defaults (if +\f1\fs18 NULL +\f2\fs20 is passed) to +\f1\fs18 1.0 +\f2\fs20 for multiplicative traits, +\f1\fs18 0.0 +\f2\fs20 for additive traits, to produce no effect. The default standard deviation for the individual offset, if +\f1\fs18 NULL +\f2\fs20 is passed, is +\f1\fs18 0.0 +\f2\fs20 . If +\f1\fs18 NULL +\f2\fs20 is passed for one of these parameters, it must be passed for both; either the default distribution is used, or it is not. Note that for multiplicative traits all effects, including individual offsets, will be clamped to a minimum of +\f1\fs18 0.0 +\f2\fs20 .\ +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.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() +\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 @@ -2669,11 +2810,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 @@ -2693,7 +2834,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() @@ -2702,7 +2843,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.\ @@ -2873,9 +3014,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.\ @@ -2901,7 +3042,7 @@ The implementation of This function assumes that mutations of type \f1\fs18 mutType \f2\fs20 encode their effect size upon the quantitative trait in their -\f1\fs18 selectionCoeff +\f1\fs18 effect \f2\fs20 property, as is fairly standard in SLiM. The implementation of \f1\fs18 calcVA() \f2\fs20 , which is viewable with diff --git a/SLiMgui/SLiMWindowController.h b/SLiMgui/SLiMWindowController.h index ad3b1f06..2139e0cf 100644 --- a/SLiMgui/SLiMWindowController.h +++ b/SLiMgui/SLiMWindowController.h @@ -179,7 +179,7 @@ class Community; // Misc bool observingKeyPaths; - SLiMFunctionGraphToolTipWindow *functionGraphToolTipWindow; // for previews of muttype DFEs or interaction type IFs + SLiMFunctionGraphToolTipWindow *functionGraphToolTipWindow; // for previews of muttype DESs or interaction type IFs } + (NSColor *)blackContrastingColorForIndex:(int)index; diff --git a/SLiMgui/SLiMWindowController.mm b/SLiMgui/SLiMWindowController.mm index 92f8bdfc..6fe77b10 100644 --- a/SLiMgui/SLiMWindowController.mm +++ b/SLiMgui/SLiMWindowController.mm @@ -2233,6 +2233,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]; @@ -4408,6 +4414,7 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu std::advance(mutTypeIter, rowIndex); slim_objectid_t mutTypeID = mutTypeIter->first; MutationType *mutationType = mutTypeIter->second; + EffectDistributionInfo &DES_info = mutationType->effect_distributions_[0]; // FIXME MULTITRAIT if (aTableColumn == mutTypeIDColumn) { @@ -4420,60 +4427,60 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColu } else if (aTableColumn == mutTypeDominanceColumn) { - return [NSString stringWithFormat:@"%.3f", mutationType->dominance_coeff_]; + return [NSString stringWithFormat:@"%.3f", DES_info.default_dominance_coeff_]; } else if (aTableColumn == mutTypeDFETypeColumn) { - switch (mutationType->dfe_type_) + switch (DES_info.DES_type_) { - case DFEType::kFixed: return @"fixed"; - case DFEType::kGamma: return @"gamma"; - case DFEType::kExponential: return @"exp"; - case DFEType::kNormal: return @"normal"; - case DFEType::kWeibull: return @"Weibull"; - case DFEType::kLaplace: return @"Laplace"; - case DFEType::kScript: return @"script"; + case DESType::kFixed: return @"fixed"; + case DESType::kGamma: return @"gamma"; + case DESType::kExponential: return @"exp"; + case DESType::kNormal: return @"normal"; + case DESType::kWeibull: return @"Weibull"; + case DESType::kLaplace: return @"Laplace"; + case DESType::kScript: return @"script"; } } else if (aTableColumn == mutTypeDFEParamsColumn) { NSMutableString *paramString = [[NSMutableString alloc] init]; - if (mutationType->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 < mutationType->dfe_strings_.size(); ++paramIndex) + // DES type 's' has parameters of type string + for (unsigned int paramIndex = 0; paramIndex < DES_info.DES_strings_.size(); ++paramIndex) { - const char *dfe_string = mutationType->dfe_strings_[paramIndex].c_str(); - NSString *ns_dfe_string = [NSString stringWithUTF8String:dfe_string]; + const char *DES_string = DES_info.DES_strings_[paramIndex].c_str(); + NSString *ns_DES_string = [NSString stringWithUTF8String:DES_string]; - [paramString appendFormat:@"\"%@\"", ns_dfe_string]; + [paramString appendFormat:@"\"%@\"", ns_DES_string]; - if (paramIndex < mutationType->dfe_strings_.size() - 1) + if (paramIndex < DES_info.DES_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) + // All other DESs have parameters of type double + for (unsigned int paramIndex = 0; paramIndex < DES_info.DES_parameters_.size(); ++paramIndex) { NSString *paramSymbol = @""; - switch (mutationType->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 appendFormat:@"%@=%.3f", paramSymbol, mutationType->dfe_parameters_[paramIndex]]; + [paramString appendFormat:@"%@=%.3f", paramSymbol, DES_info.DES_parameters_[paramIndex]]; - if (paramIndex < mutationType->dfe_parameters_.size() - 1) + if (paramIndex < DES_info.DES_parameters_.size() - 1) [paramString appendString:@", "]; } } @@ -4724,7 +4731,7 @@ - (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(NSCell *)aCell if (functionGraphToolTipWindow && ([functionGraphToolTipWindow mutType] == mutationType)) return (id _Nonnull)nil; // get rid of the static analyzer warning - //NSLog(@"show DFE tooltip view here for mut ID %d!", mutationType->mutation_type_id_); + //NSLog(@"show DES tooltip view here for mut ID %d!", mutationType->mutation_type_id_); // Make the tooltip window, configure it, and display it if (!functionGraphToolTipWindow) @@ -4765,7 +4772,7 @@ - (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(NSCell *)aCell if (functionGraphToolTipWindow && ([functionGraphToolTipWindow interactionType] == interactionType)) return (id _Nonnull)nil; // get rid of the static analyzer warning - //NSLog(@"show DFE tooltip view here for interaction ID %d!", interactionType->interaction_type_id_); + //NSLog(@"show DES tooltip view here for interaction ID %d!", interactionType->interaction_type_id_); // Make the tooltip window, configure it, and display it if (!functionGraphToolTipWindow) @@ -4805,7 +4812,7 @@ - (void)hideMiniGraphToolTipWindow } } -// Used to take down a custom tooltip window that we may have shown above, displaying a mutation type's DFE +// Used to take down a custom tooltip window that we may have shown above, displaying a mutation type's DES - (void)mouseExited:(NSEvent *)event { if (!functionGraphToolTipWindow) @@ -4826,7 +4833,7 @@ - (void)mouseExited:(NSEvent *)event if (mut_id != [functionGraphToolTipWindow mutType]->mutation_type_id_) return; - //NSLog(@" take down DFE tooltip view here for mut ID %d!", mut_id); + //NSLog(@" take down DES tooltip view here for mut ID %d!", mut_id); [mutTypeTableView removeTrackingArea:trackingArea]; } @@ -4837,7 +4844,7 @@ - (void)mouseExited:(NSEvent *)event if (int_id != [functionGraphToolTipWindow interactionType]->interaction_type_id_) return; - //NSLog(@" take down DFE tooltip view here for interaction ID %d!", int_id); + //NSLog(@" take down DES tooltip view here for interaction ID %d!", int_id); [interactionTypeTableView removeTrackingArea:trackingArea]; } 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/VERSIONS b/VERSIONS index b5c564c1..6085ffd3 100644 --- a/VERSIONS +++ b/VERSIONS @@ -12,7 +12,7 @@ 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() add the PCG random number generator, switch to pcg32_fast and pcg64_fast, remove all use of the old taus2 and MT19937-64 generators; note this completely breaks backward reproducibility @@ -40,6 +40,93 @@ development head (in the master branch): fix #602, (float)rdirichlet(integer$ n, numeric alpha) function to Eidos for Dirichlet distribution draws +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 + 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) + 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 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 + 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++ 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]) + 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 + 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) + 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$ + 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 + 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) + 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 + 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 + 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): add recipe 16.11, life-long monogamous mating add the ability to suppress the header line of a LogFile, with a new [logical$ header = T] parameter to createLogFile() (#516), and adding [Nl$ header = NULL] for setFilePath() (#516) @@ -86,7 +173,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 - version 5.0 (Eidos version 4.0): multi-chromosome transition: --- main changes to existing APIs: @@ -149,7 +235,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/chromosome.cpp b/core/chromosome.cpp index a30238fc..6241a77e 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 @@ -1033,15 +1034,14 @@ MutationIndex Chromosome::DrawNewMutation(std::pairDrawSelectionCoefficient(); - // 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, p_subpop_index, p_tick, -1); + 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 @@ -1404,18 +1404,17 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pairDrawSelectionCoefficient(); - // 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, p_subpop_index, p_tick, nucleotide); + 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) { - 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) @@ -1433,13 +1432,12 @@ MutationIndex Chromosome::DrawNewMutationExtended(std::pairBlockIndex(); + MutationIndex post_callback_mut_index = mutation_block->IndexInBlock(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 @@ -1775,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_) @@ -3755,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 d4dedcfa..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 @@ -198,6 +199,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 e08530f9..c6a5ebbb 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 @@ -555,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(); @@ -637,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) { @@ -724,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)) { @@ -745,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)) { @@ -1005,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 @@ -1039,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); @@ -2271,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); @@ -2371,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); @@ -2551,30 +2571,45 @@ 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; - for (int registry_index = 0; registry_index < registry_size; ++registry_index) + if (registry_size) { - MutationIndex mutation_index = registry[registry_index]; + MutationBlock *mutationBlock = species->SpeciesMutationBlock(); + MutationIndex mutBlockCapacity = mutationBlock->capacity_; + + 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); + + // check mutation integrity + Mutation *mut = mutationBlock->MutationForIndex(mutation_index); + + mut->SelfConsistencyCheck(" in AllSpecies_CheckIntegrity()"); + } + + size_t original_size = indices.size(); - 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(); + std::sort(indices.begin(), indices.end()); + indices.resize(static_cast(std::distance(indices.begin(), std::unique(indices.begin(), indices.end())))); - indices.push_back(mutation_index); + 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(); + // Check the integrity of all substitution objects + for (Substitution *sub : species->population_.substitutions_) + sub->SelfConsistencyCheck(" in AllSpecies_CheckIntegrity()"); } #endif } @@ -2619,7 +2654,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); @@ -2651,7 +2686,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); @@ -2816,7 +2851,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); @@ -2859,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; } @@ -2982,7 +3017,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); @@ -3118,7 +3153,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); @@ -3162,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; } @@ -3279,7 +3314,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); @@ -3405,8 +3440,22 @@ 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_) + { + 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 { diff --git a/core/community.h b/core/community.h index fd9f3510..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 - @@ -213,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, 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/community_eidos.cpp b/core/community_eidos.cpp index 8599336d..c771048b 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); @@ -118,11 +120,12 @@ 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)); 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); @@ -970,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 @@ -1331,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 @@ -1350,6 +1365,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/core.pro b/core/core.pro index 7e28f9e6..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 \ @@ -113,7 +114,8 @@ SOURCES += \ species.cpp \ species_eidos.cpp \ subpopulation.cpp \ - substitution.cpp + substitution.cpp \ + trait.cpp HEADERS += \ chromosome.h \ @@ -124,6 +126,7 @@ HEADERS += \ individual.h \ interaction_type.h \ log_file.h \ + mutation_block.h \ mutation_run.h \ mutation_type.h \ mutation.h \ @@ -138,4 +141,5 @@ HEADERS += \ spatial_map.h \ species.h \ subpopulation.h \ - substitution.h + substitution.h \ + trait.h diff --git a/core/genomic_element.cpp b/core/genomic_element.cpp index 7b35e110..f2f217da 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) @@ -211,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 d40c5db1..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 @@ -76,10 +77,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 12e90a7e..c5a4a124 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) @@ -405,7 +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->dfe_type_ != DFEType::kFixed) || (mutation_type_ptr->dfe_parameters_[0] != 0.0)) + if (!mutation_type_ptr->all_neutral_DES_) species_.pure_neutral_ = false; } @@ -448,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 fca94114..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 @@ -102,8 +103,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 6637f155..e21536d3 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"); @@ -447,7 +448,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); @@ -483,8 +484,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) Species *consensus_species = Community::SpeciesForHaplosomesVector((Haplosome **)p_values, (int)p_values_size); EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); @@ -524,8 +526,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) @@ -539,8 +542,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) @@ -553,8 +557,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) @@ -591,8 +596,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 @@ -863,7 +869,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; @@ -918,7 +924,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; @@ -1274,7 +1280,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) { @@ -1301,6 +1307,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()) @@ -1310,8 +1318,9 @@ 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; - double selcoeff_sum = 0.0; + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + Mutation *mut_block_ptr = mutation_block->mutation_buffer_; + double effect_sum = 0.0; int mutrun_count = mutrun_count_; for (int run_index = 0; run_index < mutrun_count; ++run_index) @@ -1320,22 +1329,26 @@ 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); + effect_sum += (double)mut_trait_info[0].effect_size_; + } } } - return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(selcoeff_sum)); + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(effect_sum)); } // 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 @@ -1427,9 +1440,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 @@ -1697,7 +1710,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 = gSLiM_Mutation_Block; + 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 @@ -1962,7 +1976,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 << ";"; @@ -1971,7 +1990,17 @@ 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_; + + // 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); + + 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 << ";"; @@ -2094,9 +2123,19 @@ 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->mutation_type_ptr_->dominance_coeff_ << ";"; + p_out << "S=" << mut_trait_info->effect_size_ << ";"; + + 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_ << ";"; @@ -2188,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 @@ -2227,7 +2266,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("effect")->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)); @@ -2293,6 +2332,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. @@ -2526,12 +2567,12 @@ 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 + // Similarly, no need to check and set pure_neutral_ and all_neutral_mutations_; the mutation is already in the system } } } @@ -2558,7 +2599,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)); } } @@ -2566,7 +2607,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addMutations(EidosGlobalStringID p_ } // ********************* + (object)addNewDrawnMutation(io mutationType, integer position, [Nio originSubpop = NULL], [Nis nucleotide = NULL]) -// ********************* + (object)addNewMutation(io mutationType, numeric selectionCoeff, integer position, [Nio originSubpop = NULL], [Nis nucleotide = NULL]) +// ********************* + (object)addNewMutation(io mutationType, numeric effect, integer position, [Nio originSubpop = NULL], [Nis nucleotide = NULL]) // EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const { @@ -2577,7 +2618,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID #endif EidosValue *arg_muttype = p_arguments[0].get(); - EidosValue *arg_selcoeff = (p_method_id == gID_addNewDrawnMutation ? nullptr : p_arguments[1].get()); + EidosValue *arg_effect = (p_method_id == gID_addNewDrawnMutation ? nullptr : p_arguments[1].get()); EidosValue *arg_position = (p_method_id == gID_addNewDrawnMutation ? p_arguments[1].get() : p_arguments[2].get()); EidosValue *arg_origin_subpop = (p_method_id == gID_addNewDrawnMutation ? p_arguments[2].get() : p_arguments[3].get()); EidosValue *arg_nucleotide = (p_method_id == gID_addNewDrawnMutation ? p_arguments[3].get() : p_arguments[4].get()); @@ -2599,6 +2640,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. @@ -2687,7 +2730,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID // position and originSubpop can now be either singletons or vectors of matching length or NULL; check them all int muttype_count = arg_muttype->Count(); - int selcoeff_count = (arg_selcoeff ? arg_selcoeff->Count() : 0); + int effect_count = (arg_effect ? arg_effect->Count() : 0); int position_count = arg_position->Count(); int origin_subpop_count = arg_origin_subpop->Count(); int nucleotide_count = arg_nucleotide->Count(); @@ -2697,14 +2740,14 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_addNewMutation(EidosGlobalStringID if (arg_nucleotide->Type() == EidosValueType::kValueNULL) nucleotide_count = 1; - int count_to_add = std::max({muttype_count, selcoeff_count, position_count, origin_subpop_count, nucleotide_count}); + int count_to_add = std::max({muttype_count, effect_count, position_count, origin_subpop_count, nucleotide_count}); if (((muttype_count != 1) && (muttype_count != count_to_add)) || - (arg_selcoeff && (selcoeff_count != 1) && (selcoeff_count != count_to_add)) || + (arg_effect && (effect_count != 1) && (effect_count != count_to_add)) || ((position_count != 1) && (position_count != count_to_add)) || ((origin_subpop_count != 1) && (origin_subpop_count != count_to_add)) || ((nucleotide_count != 1) && (nucleotide_count != count_to_add))) - EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_addNewMutation): " << method_name << " requires that mutationType, " << ((p_method_id == gID_addNewMutation) ? "selectionCoeff, " : "") << "position, originSubpop, and nucleotide be either (1) singleton, or (2) equal in length to the other non-singleton argument(s), or (3) NULL, for originSubpop and nucleotide." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_addNewMutation): " << method_name << " requires that mutationType, " << ((p_method_id == gID_addNewMutation) ? "effect, " : "") << "position, originSubpop, and nucleotide be either (1) singleton, or (2) equal in length to the other non-singleton argument(s), or (3) NULL, for originSubpop and nucleotide." << EidosTerminate(); EidosValue_Object_SP retval(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(gSLiM_Mutation_Class)); @@ -2815,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 - double singleton_selection_coeff = (arg_selcoeff ? arg_selcoeff->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)); @@ -2870,7 +2913,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; @@ -2886,14 +2928,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->DrawSelectionCoefficient(); - } - if (origin_subpop_count != 1) { if (arg_origin_subpop->Type() == EidosValueType::kValueInt) @@ -2911,16 +2945,40 @@ 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(); - - 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); + MutationIndex new_mut_index = mutation_block->NewMutationFromBlock(); + Mutation *new_mut; - // 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_neutral_DES_) + species->pure_neutral_ = false; + } + else // (p_method_id == gID_addNewMutation) + { + slim_effect_t selection_coeff = singleton_selection_coeff; + + if (effect_count != 1) + { + if (arg_effect) + selection_coeff = (slim_effect_t)arg_effect->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... 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_ + // The selection coefficient was supplied by the user (i.e., not be from the mutation type's DES), so we set all_neutral_mutations_ also + if (selection_coeff != (slim_effect_t)0.0) + { + species->pure_neutral_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; + } } // add to the registry, return value, haplosome, etc. @@ -2934,11 +2992,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); @@ -2956,7 +3010,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 @@ -2971,12 +3025,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; } } @@ -3174,9 +3228,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); } @@ -3209,10 +3263,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); @@ -3270,6 +3324,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(); @@ -3396,7 +3453,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(); + 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; @@ -3412,17 +3469,19 @@ 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, selection_coeff, subpop_index, origin_tick, nucleotide); + // 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_ - if (selection_coeff != 0.0) + if (selection_coeff != (slim_effect_t)0.0) { species.pure_neutral_ = false; - // the selection coefficient was drawn from the mutation type's DFE, so there is no need to set all_pure_neutral_DFE_ - //mutation_type_ptr->all_pure_neutral_DFE_ = 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 @@ -3431,7 +3490,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_;}); @@ -3484,10 +3542,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)); } } } @@ -3524,6 +3582,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); @@ -3737,8 +3798,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_effects; + std::vector info_domcoeffs; std::vector info_poporigin; std::vector info_tickorigin; std::vector info_muttype; @@ -3775,7 +3836,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt std::vector value_substrs = Eidos_string_split(info_substr.substr(2), ","); for (std::string &value_substr : value_substrs) - info_selcoeffs.emplace_back(EidosInterpreter::FloatForString(value_substr, nullptr)); + info_effects.emplace_back(EidosInterpreter::FloatForString(value_substr, nullptr)); } else if (info_DOM_defined && (info_substr.compare(0, 4, "DOM=") == 0)) // Dominance Coefficient { @@ -3829,7 +3890,7 @@ EidosValue_SP Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF(EidosGlobalSt if ((info_mutids.size() != 0) && (info_mutids.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file unexpected value count for MID field." << EidosTerminate(); - if ((info_selcoeffs.size() != 0) && (info_selcoeffs.size() != alt_allele_count)) + if ((info_effects.size() != 0) && (info_effects.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file unexpected value count for S field." << EidosTerminate(); if ((info_domcoeffs.size() != 0) && (info_domcoeffs.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Haplosome_Class::ExecuteMethod_readHaplosomesFromVCF): VCF file unexpected value count for DOM field." << EidosTerminate(); @@ -3965,20 +4026,22 @@ 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_effect_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->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 - double selection_coeff; + // get the selection coefficient from S, or draw one from the mutation type + slim_effect_t selection_coeff; - if (info_selcoeffs.size() > 0) - selection_coeff = info_selcoeffs[alt_allele_index]; + if (info_effects.size() > 0) + selection_coeff = info_effects[alt_allele_index]; else - selection_coeff = 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; @@ -4052,7 +4115,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) @@ -4060,20 +4123,21 @@ 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 (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, subpop_index, origin_tick, nucleotide); + // 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); } // 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) + // The selection coefficient might have been supplied by the user (i.e., not be from the mutation type's DES), so we set all_neutral_mutations_ also + if (selection_coeff != (slim_effect_t)0.0) { species->pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DFE_ = 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 @@ -4113,16 +4177,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); @@ -4140,7 +4203,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) @@ -4152,6 +4214,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(); @@ -4326,6 +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(); for (int value_index = 0; value_index < mutations_count; ++value_index) { @@ -4339,8 +4405,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_ != (slim_effect_t)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_;}); @@ -4485,7 +4563,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; } } @@ -4543,7 +4621,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) { @@ -4596,7 +4674,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)); } } } @@ -4633,6 +4711,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... @@ -4655,7 +4738,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) @@ -4693,7 +4776,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; @@ -4726,7 +4809,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; @@ -4761,8 +4844,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; @@ -4802,7 +4885,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 48e574bc..7bc7801d 100644 --- a/core/haplosome.h +++ b/core/haplosome.h @@ -56,15 +56,16 @@ typedef std::unordered_map SLiMBulkOpera typedef std::pair SLiMBulkOperationPair; #endif - class Species; class Population; class Subpopulation; class Individual; 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 @@ -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); @@ -437,13 +438,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; @@ -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 63ab22e4..740aa061 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" @@ -53,7 +54,8 @@ 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() -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) : +// 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, slim_fitness_t p_fitness, float p_mean_parent_age) : #ifdef SLIMGUI color_set_(false), #endif @@ -82,6 +84,10 @@ 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 + trait_info_ = nullptr; + _InitializePerTraitInformation(); + // Initialize tag values to the "unset" value tag_value_ = SLIM_TAG_UNSET_VALUE; tagF_value_ = SLIM_TAGF_UNSET_VALUE; @@ -99,6 +105,85 @@ Individual::Individual(Subpopulation *p_subpopulation, slim_popsize_t p_individu #endif } +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, 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 + // 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) + { +#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_.phenotype_ = std::numeric_limits::quiet_NaN(); // "uncalculated" + 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 + { +#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(IndividualTraitInfo))); + + for (int 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(); + } + } +} + Individual::~Individual(void) { // BCH 10/6/2024: Individual now owns the haplosomes inside it (a policy change for multichrom) @@ -131,8 +216,12 @@ Individual::~Individual(void) if (haplosomes_ != hapbuffer_) free(haplosomes_); + if (trait_info_ != &trait_info_0_) + free(trait_info_); + #if DEBUG haplosomes_ = nullptr; + trait_info_ = nullptr; #endif } @@ -812,7 +901,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) @@ -1322,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 { @@ -1409,7 +1498,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()) { @@ -1631,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 { @@ -1667,12 +1756,26 @@ EidosValue_SP Individual::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: + { + // Here we implement a special behavior: you can do individual. to access a trait value directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). + Species &species = subpopulation_->species_; + Trait *trait = species.TraitFromStringID(p_property_id); + + if (trait) + { + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)trait_info_[trait->Index()].phenotype_)); + } + return super::GetProperty(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) @@ -1685,8 +1788,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) Species *consensus_species = Community::SpeciesForIndividualsVector((Individual **)p_values, (int)p_values_size); EidosValue_Int *int_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Int())->resize_no_initialize(p_values_size); @@ -1726,8 +1830,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) @@ -1744,8 +1849,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(); @@ -1761,8 +1867,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(); @@ -1778,8 +1885,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) @@ -1796,8 +1904,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) @@ -1813,8 +1922,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) @@ -1830,8 +1940,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) @@ -1847,8 +1958,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) @@ -1864,8 +1976,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) @@ -1881,8 +1994,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) @@ -1895,36 +2009,39 @@ 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) { 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; } -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) { 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; } -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) @@ -1937,8 +2054,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) @@ -1951,8 +2069,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; @@ -2034,8 +2153,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) @@ -2051,8 +2171,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 @@ -2119,8 +2240,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 @@ -2191,8 +2313,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 @@ -2259,8 +2382,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 @@ -2331,8 +2455,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 @@ -2361,8 +2486,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 @@ -2391,6 +2517,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((double)value->trait_info_[trait_index].phenotype_, 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((double)value->trait_info_[trait_index].phenotype_, 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 @@ -2476,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; @@ -2509,12 +2670,27 @@ void Individual::SetProperty(EidosGlobalStringID p_property_id, const EidosValue // all others, including gID_none default: + { + // Here we implement a special behavior: you can do individual. to access a trait value directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). + Species &species = subpopulation_->species_; + Trait *trait = species.TraitFromStringID(p_property_id); + + if (trait) // ACCELERATED + { + trait_info_[trait->Index()].phenotype_ = (slim_effect_t)p_value.FloatAtIndex_NOCAST(0, nullptr); + return; + } + return super::SetProperty(p_property_id, p_value); + } } } -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 @@ -2534,8 +2710,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 @@ -2555,8 +2732,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(); @@ -2586,8 +2764,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(); @@ -2616,8 +2795,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(); @@ -2646,8 +2826,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(); @@ -2676,8 +2857,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(); @@ -2719,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; } @@ -2742,14 +2924,15 @@ 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; } -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; @@ -2770,8 +2953,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); @@ -2788,8 +2972,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); @@ -2806,8 +2991,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); @@ -2824,9 +3010,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) @@ -2881,8 +3067,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); @@ -2900,6 +3087,69 @@ 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(); + + if (p_source_size == 1) + { + slim_effect_t source_value = (slim_effect_t)source_data[0]; + + for (size_t value_index = 0; value_index < p_values_size; ++value_index) + { + const Individual *value = individuals_buffer[value_index]; + + value->trait_info_[trait_index].phenotype_ = source_value; + } + } + 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].phenotype_ = (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 + if (p_source_size == 1) + { + slim_effect_t source_value = (slim_effect_t)source_data[0]; + + 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].phenotype_ = 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].phenotype_ = (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) @@ -2907,6 +3157,8 @@ 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_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); @@ -3014,7 +3266,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(); @@ -3087,7 +3339,76 @@ EidosValue_SP Individual::ExecuteMethod_haplosomesForChromosomes(EidosGlobalStri return EidosValue_SP(vec); } + +// ********************* - (float)offsetForTrait([Niso trait = NULL]) +// +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 + 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 = trait_info_[trait_index].offset_; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)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 = trait_info_[trait_index].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) +{ +#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((double)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((double)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) @@ -3205,6 +3526,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); @@ -3217,7 +3540,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 = gSLiM_Mutation_Block; + 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(); @@ -3226,7 +3550,7 @@ EidosValue_SP Individual::ExecuteMethod_Accelerated_sumOfMutationsOfType(EidosOb for (size_t element_index = 0; element_index < p_elements_size; ++element_index) { Individual *element = (Individual *)(p_elements[element_index]); - double selcoeff_sum = 0.0; + double effect_sum = 0.0; for (int haplosome_index = 0; haplosome_index < haplosome_count_per_individual; haplosome_index++) { @@ -3242,18 +3566,22 @@ 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); + effect_sum += (double)mut_trait_info[0].effect_size_; + } } } } } - float_result->set_float_no_check(selcoeff_sum, element_index); + float_result->set_float_no_check(effect_sum, element_index); } return EidosValue_SP(float_result); @@ -3326,7 +3654,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()) { @@ -3598,7 +3926,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) { @@ -3868,7 +4196,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 @@ -3936,6 +4264,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))->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()); @@ -3956,6 +4289,9 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ { switch (p_method_id) { + case gID_demandPhenotype: return ExecuteMethod_demandPhenotype(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setOffsetForTrait: return ExecuteMethod_setOffsetForTrait(p_method_id, p_target, p_arguments, p_interpreter); + case gID_setPhenotypeForTrait: return ExecuteMethod_setPhenotypeForTrait(p_method_id, p_target, p_arguments, p_interpreter); case gID_outputIndividuals: return ExecuteMethod_outputIndividuals(p_method_id, p_target, p_arguments, p_interpreter); case gID_outputIndividualsToVCF: return ExecuteMethod_outputIndividualsToVCF(p_method_id, p_target, p_arguments, p_interpreter); case gID_readIndividualsFromVCF: return ExecuteMethod_readIndividualsFromVCF(p_method_id, p_target, p_arguments, p_interpreter); @@ -3972,92 +4308,438 @@ EidosValue_SP Individual_Class::ExecuteClassMethod(EidosGlobalStringID p_method_ } } -// ********************* + (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]) +// ********************* + (void)setOffsetForTrait([Niso trait = NULL], [Nif offset = NULL]) // -EidosValue_SP Individual_Class::ExecuteMethod_outputIndividuals(EidosGlobalStringID p_method_id, EidosValue_Object *p_target, const std::vector &p_arguments, EidosInterpreter &p_interpreter) const +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_arguments, p_interpreter) - EidosValue *filePath_value = p_arguments[0].get(); - EidosValue *append_value = p_arguments[1].get(); - EidosValue *chromosome_value = p_arguments[2].get(); - EidosValue *spatialPositions_value = p_arguments[3].get(); - EidosValue *ages_value = p_arguments[4].get(); - EidosValue *ancestralNucleotides_value = p_arguments[5].get(); - EidosValue *pedigreeIDs_value = p_arguments[6].get(); - EidosValue *objectTags_value = p_arguments[7].get(); +#pragma unused (p_method_id, p_interpreter) + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *offset_value = p_arguments[1].get(); - // here we need to require at least one target individual, - // do a species consistency check and get the species/community, - // and get the vector of individuals that we will pass in - // (from the raw data of the EidosValue, no need to copy; but add const) int individuals_count = p_target->Count(); + int offset_count = offset_value->Count(); if (individuals_count == 0) - EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() cannot be called on a zero-length target vector; at least one individual is required." << EidosTerminate(); + return gStaticEidosValueVOID; - const Individual **individuals_buffer = (const Individual **)p_target->ObjectData(); + Individual **individuals_buffer = (Individual **)p_target->ObjectData(); // SPECIES CONSISTENCY CHECK Species *species = Community::SpeciesForIndividuals(p_target); if (!species) - EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() requires that all individuals belong to the same species." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_setOffsetForTrait): setOffsetForTrait() requires that all individuals belong to the same species." << EidosTerminate(); - Community &community = species->community_; + // get the trait indices, with bounds-checking + std::vector trait_indices; + species->GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setOffsetForTrait"); + int trait_count = (int)trait_indices.size(); - // TIMING RESTRICTION - if (!community.warned_early_output_) + if (offset_value->Type() == EidosValueType::kValueNULL) { - if ((community.CycleStage() == SLiMCycleStage::kWFStage0ExecuteFirstScripts) || - (community.CycleStage() == SLiMCycleStage::kWFStage1ExecuteEarlyScripts)) + // pattern 1: drawing a default offset value for each trait in one or more individuals + for (int64_t trait_index : trait_indices) { - if (!gEidosSuppressWarnings) + Trait *trait = species->Traits()[trait_index]; + + if (trait->Type() == TraitType::kMultiplicative) { - p_interpreter.ErrorOutputStream() << "#WARNING (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() should probably not be called from a first() or early() event in a WF model; the output will reflect state at the beginning of the cycle, not the end." << std::endl; - community.warned_early_output_ = true; + 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 < (slim_effect_t)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(); + } } } } - - Chromosome *chromosome = species->GetChromosomeFromEidosValue(chromosome_value); // NULL returns nullptr - - bool output_spatial_positions = spatialPositions_value->LogicalAtIndex_NOCAST(0, nullptr); - bool output_ages = ages_value->LogicalAtIndex_NOCAST(0, nullptr); - bool output_ancestral_nucs = ancestralNucleotides_value->LogicalAtIndex_NOCAST(0, nullptr); - bool output_pedigree_ids = pedigreeIDs_value->LogicalAtIndex_NOCAST(0, nullptr); - bool output_object_tags = objectTags_value->LogicalAtIndex_NOCAST(0, nullptr); - - if (output_pedigree_ids && !species->PedigreesEnabledByUser()) - EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() cannot output pedigree IDs, because pedigree recording has not been enabled." << EidosTerminate(); - - if (filePath_value->Type() == EidosValueType::kValueNULL) + else if (offset_count == 1) { - // before writing anything, erase a progress line if we've got one up, to try to make a clean slate - Eidos_EraseProgress(); - - std::ostream &output_stream = p_interpreter.ExecutionOutputStream(); + // 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)); - Individual::PrintIndividuals_SLiM(output_stream, individuals_buffer, individuals_count, *species, output_spatial_positions, output_ages, output_ancestral_nucs, output_pedigree_ids, output_object_tags, /* p_output_substitutions */ false, chromosome); + for (int64_t trait_index : trait_indices) + { + 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 < (slim_effect_t)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_for_trait; + } } - else + else if (offset_count == trait_count) { - std::string outfile_path = Eidos_ResolvedPath(filePath_value->StringAtIndex_NOCAST(0, nullptr)); - bool append = append_value->LogicalAtIndex_NOCAST(0, nullptr); - std::ofstream outfile; + // pattern 3: setting one offset value per trait, in one or more individuals + int offset_index = 0; - outfile.open(outfile_path.c_str(), append ? (std::ios_base::app | std::ios_base::out) : std::ios_base::out); - - if (outfile.is_open()) + for (int64_t trait_index : trait_indices) { - Individual::PrintIndividuals_SLiM(outfile, individuals_buffer, individuals_count, *species, output_spatial_positions, output_ages, output_ancestral_nucs, output_pedigree_ids, output_object_tags, /* p_output_substitutions */ false, chromosome); + Trait *trait = species->Traits()[trait_index]; + slim_effect_t offset = static_cast(offset_value->NumericAtIndex_NOCAST(offset_index++, nullptr)); - outfile.close(); + // effects for multiplicative traits are clamped to a minimum of 0.0 + if ((trait->Type() == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) + offset = 0.0; + + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + ind->trait_info_[trait_index].offset_ = offset; + } } - else + } + 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) { - EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() could not open " << outfile_path << "." << EidosTerminate(); + // 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]; + Trait *trait = species->Traits()[trait_index]; + + 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 < (slim_effect_t)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 + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + { + 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 < (slim_effect_t)0.0)) + offset = 0.0; + + ind->trait_info_[trait_index].offset_ = offset; + } + } + } } - } + 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]; + Trait *trait = species->Traits()[trait_index]; + + 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 < (slim_effect_t)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 + { + for (int individual_index = 0; individual_index < individuals_count; ++individual_index) + { + Individual *ind = individuals_buffer[individual_index]; + + for (int64_t trait_index : trait_indices) + { + 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 < (slim_effect_t)0.0)) + offset = 0.0; + + ind->trait_info_[trait_index].offset_ = offset; + } + } + } + } + } + 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)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 +{ +#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 +{ +#pragma unused (p_method_id, p_arguments, p_interpreter) + EidosValue *filePath_value = p_arguments[0].get(); + EidosValue *append_value = p_arguments[1].get(); + EidosValue *chromosome_value = p_arguments[2].get(); + EidosValue *spatialPositions_value = p_arguments[3].get(); + EidosValue *ages_value = p_arguments[4].get(); + EidosValue *ancestralNucleotides_value = p_arguments[5].get(); + EidosValue *pedigreeIDs_value = p_arguments[6].get(); + EidosValue *objectTags_value = p_arguments[7].get(); + + // here we need to require at least one target individual, + // do a species consistency check and get the species/community, + // and get the vector of individuals that we will pass in + // (from the raw data of the EidosValue, no need to copy; but add const) + int individuals_count = p_target->Count(); + + if (individuals_count == 0) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() cannot be called on a zero-length target vector; at least one individual is required." << EidosTerminate(); + + const Individual **individuals_buffer = (const Individual **)p_target->ObjectData(); + + // SPECIES CONSISTENCY CHECK + Species *species = Community::SpeciesForIndividuals(p_target); + + if (!species) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() requires that all individuals belong to the same species." << EidosTerminate(); + + Community &community = species->community_; + + // TIMING RESTRICTION + if (!community.warned_early_output_) + { + if ((community.CycleStage() == SLiMCycleStage::kWFStage0ExecuteFirstScripts) || + (community.CycleStage() == SLiMCycleStage::kWFStage1ExecuteEarlyScripts)) + { + if (!gEidosSuppressWarnings) + { + p_interpreter.ErrorOutputStream() << "#WARNING (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() should probably not be called from a first() or early() event in a WF model; the output will reflect state at the beginning of the cycle, not the end." << std::endl; + community.warned_early_output_ = true; + } + } + } + + Chromosome *chromosome = species->GetChromosomeFromEidosValue(chromosome_value); // NULL returns nullptr + + bool output_spatial_positions = spatialPositions_value->LogicalAtIndex_NOCAST(0, nullptr); + bool output_ages = ages_value->LogicalAtIndex_NOCAST(0, nullptr); + bool output_ancestral_nucs = ancestralNucleotides_value->LogicalAtIndex_NOCAST(0, nullptr); + bool output_pedigree_ids = pedigreeIDs_value->LogicalAtIndex_NOCAST(0, nullptr); + bool output_object_tags = objectTags_value->LogicalAtIndex_NOCAST(0, nullptr); + + if (output_pedigree_ids && !species->PedigreesEnabledByUser()) + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() cannot output pedigree IDs, because pedigree recording has not been enabled." << EidosTerminate(); + + if (filePath_value->Type() == EidosValueType::kValueNULL) + { + // before writing anything, erase a progress line if we've got one up, to try to make a clean slate + Eidos_EraseProgress(); + + std::ostream &output_stream = p_interpreter.ExecutionOutputStream(); + + Individual::PrintIndividuals_SLiM(output_stream, individuals_buffer, individuals_count, *species, output_spatial_positions, output_ages, output_ancestral_nucs, output_pedigree_ids, output_object_tags, /* p_output_substitutions */ false, chromosome); + } + else + { + std::string outfile_path = Eidos_ResolvedPath(filePath_value->StringAtIndex_NOCAST(0, nullptr)); + bool append = append_value->LogicalAtIndex_NOCAST(0, nullptr); + std::ofstream outfile; + + outfile.open(outfile_path.c_str(), append ? (std::ios_base::app | std::ios_base::out) : std::ios_base::out); + + if (outfile.is_open()) + { + Individual::PrintIndividuals_SLiM(outfile, individuals_buffer, individuals_count, *species, output_spatial_positions, output_ages, output_ancestral_nucs, output_pedigree_ids, output_object_tags, /* p_output_substitutions */ false, chromosome); + + outfile.close(); + } + else + { + EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_outputIndividuals): outputIndividuals() could not open " << outfile_path << "." << EidosTerminate(); + } + } return gStaticEidosValueVOID; } @@ -4151,7 +4833,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; @@ -4177,10 +4859,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]) @@ -4203,6 +4885,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(); @@ -4469,8 +5153,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_effects; + std::vector info_domcoeffs; std::vector info_poporigin; std::vector info_tickorigin; std::vector info_muttype; @@ -4507,7 +5191,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal std::vector value_substrs = Eidos_string_split(info_substr.substr(2), ","); for (std::string &value_substr : value_substrs) - info_selcoeffs.emplace_back(EidosInterpreter::FloatForString(value_substr, nullptr)); + info_effects.emplace_back(EidosInterpreter::FloatForString(value_substr, nullptr)); } else if (info_DOM_defined && (info_substr.compare(0, 4, "DOM=") == 0)) // Dominance Coefficient { @@ -4561,7 +5245,7 @@ EidosValue_SP Individual_Class::ExecuteMethod_readIndividualsFromVCF(EidosGlobal if ((info_mutids.size() != 0) && (info_mutids.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file unexpected value count for MID field." << EidosTerminate(); - if ((info_selcoeffs.size() != 0) && (info_selcoeffs.size() != alt_allele_count)) + if ((info_effects.size() != 0) && (info_effects.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file unexpected value count for S field." << EidosTerminate(); if ((info_domcoeffs.size() != 0) && (info_domcoeffs.size() != alt_allele_count)) EIDOS_TERMINATION << "ERROR (Individual_Class::ExecuteMethod_readIndividualsFromVCF): VCF file unexpected value count for DOM field." << EidosTerminate(); @@ -4594,20 +5278,22 @@ 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_effect_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->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 - double selection_coeff; + // get the selection coefficient from S, or draw one from the mutation type + slim_effect_t selection_coeff; - if (info_selcoeffs.size() > 0) - selection_coeff = info_selcoeffs[alt_allele_index]; + if (info_effects.size() > 0) + selection_coeff = info_effects[alt_allele_index]; else - selection_coeff = mutation_type_ptr->DrawSelectionCoefficient(); + 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; @@ -4681,7 +5367,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) @@ -4689,19 +5375,20 @@ 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 (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, subpop_index, origin_tick, nucleotide); + // 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); } // 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_pure_neutral_DFE_ = 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 @@ -4914,14 +5601,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 @@ -4935,7 +5622,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 @@ -4955,7 +5642,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 @@ -4967,7 +5654,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 @@ -4984,7 +5671,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); @@ -5190,6 +5876,973 @@ EidosValue_SP Individual_Class::ExecuteMethod_setSpatialPosition(EidosGlobalStri } +// +// Phenotype demand +// +#pragma mark - +#pragma mark Phenotype demand +#pragma mark - + +// ********************* + (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 +{ +#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 < 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. + // 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 re-enable non-neutral caches and recache non-neutral caches first + + // For a given individual, for a given trait, we have to make a decision as to whether we will recalculate or not. That decision gets made + // once and then holds across all chromosomes for the individual. But we're looping over chromosomes at the topmost level, so we have a + // little problem: how will we remember whether we decided to recalculate a given individual/trait when we get to doing the work for + // successive chromosomes? We have to keep a vector of flags, actually; there's no choice but to keep that state somewhere. So, we use + // std::vector with # individuals x # traits flags in it. Here we loop through individuals and traits, making decisions about whether + // we're recalculating each trait in each individual. For f_force_recalc == true that decision is always YES, so this method is templated + // to avoid all overhead completely in that case. For each phenotype that we do intend to recalculate, we set its initial value from the + // baseline offset and individual offset for the trait. + // FIXME MULTITRAIT: if we have just one chromosome and one trait, we don't need this std::vector, because the decision is only needed + // once per individual; that should probably be special-cased, since this bool vector thing is gross and heavyweight. + std::vector recalc_decisions; + + if (!f_force_recalc) + recalc_decisions.resize(individuals_count * trait_indices.size()); // zero-fills to false + + for (int trait_indices_index = 0; trait_indices_index < trait_indices_count; trait_indices_index++) + { + 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 <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= effect; + } + } + } + + trait_info_[trait_index].phenotype_ = effect_accumulator; +} + +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 <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + + if (++haplosome1_iter == haplosome1_max) + break; + else { + haplosome1_mutindex = *haplosome1_iter; + haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; + } + } + else if (haplosome1_iter_position > haplosome2_iter_position) + { + // Process a mutation in haplosome2 since it is leading + Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; + + if (f_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 <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + + if (++haplosome2_iter == haplosome2_max) + break; + else { + haplosome2_mutindex = *haplosome2_iter; + haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + } + } + else + { + // Look for homozygosity: haplosome1_iter_position == haplosome2_iter_position + slim_position_t position = haplosome1_iter_position; + const MutationIndex *haplosome1_start = haplosome1_iter; + + // advance through haplosome1 as long as we remain at the same position, handling one mutation at a time + do + { + const MutationIndex *haplosome2_matchscan = haplosome2_iter; + + // advance through haplosome2 with haplosome2_matchscan, looking for a match for the current mutation in haplosome1, to determine whether we are homozygous or not + while (haplosome2_matchscan != haplosome2_max && (mut_block_ptr + *haplosome2_matchscan)->position_ == position) + { + if (haplosome1_mutindex == *haplosome2_matchscan) + { + // a match was found, so we multiply our fitness by the full homozygous effect + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t homozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].homozygous_effect_; + + if (f_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 <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= homozygous_effect; + } + goto homozygousExit1; + } + + haplosome2_matchscan++; + } + + // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient + { + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; + + if (f_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 <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + + homozygousExit1: + + if (++haplosome1_iter == haplosome1_max) + break; + else { + haplosome1_mutindex = *haplosome1_iter; + haplosome1_iter_position = (mut_block_ptr + haplosome1_mutindex)->position_; + } + } while (haplosome1_iter_position == position); + + // advance through haplosome2 as long as we remain at the same position, handling one mutation at a time + do + { + const MutationIndex *haplosome1_matchscan = haplosome1_start; + + // advance through haplosome1 with haplosome1_matchscan, looking for a match for the current mutation in haplosome2, to determine whether we are homozygous or not + while ((haplosome1_matchscan != haplosome1_max) && ((mut_block_ptr + *haplosome1_matchscan)->position_ == position)) + { + if (haplosome2_mutindex == *haplosome1_matchscan) + { + // a match was found; we know this match was already found by the haplosome1 loop above, so our fitness has already been multiplied appropriately + goto homozygousExit2; + } + + haplosome1_matchscan++; + } + + // no match was found, so we are heterozygous; we multiply our fitness by the selection coefficient and the dominance coefficient + { + Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; + + if (f_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 <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + + homozygousExit2: + + if (++haplosome2_iter == haplosome2_max) + break; + else { + haplosome2_mutindex = *haplosome2_iter; + haplosome2_iter_position = (mut_block_ptr + haplosome2_mutindex)->position_; + } + } while (haplosome2_iter_position == position); + + // break out if either haplosome has reached its end + if (haplosome1_iter == haplosome1_max || haplosome2_iter == haplosome2_max) + break; + } + } while (true); + } + + // one or the other haplosome has now reached its end, so now we just need to handle the remaining mutations in the unfinished haplosome +#if DEBUG + assert(!(haplosome1_iter != haplosome1_max && haplosome2_iter != haplosome2_max)); +#endif + + // if haplosome1 is unfinished, finish it + while (haplosome1_iter != haplosome1_max) + { + MutationIndex haplosome1_mutindex = *haplosome1_iter++; + Mutation *mutation = mut_block_ptr + haplosome1_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome1_mutindex)[trait_index].heterozygous_effect_; + + if (f_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 <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + + // if haplosome2 is unfinished, finish it + while (haplosome2_iter != haplosome2_max) + { + MutationIndex haplosome2_mutindex = *haplosome2_iter++; + Mutation *mutation = mut_block_ptr + haplosome2_mutindex; + slim_effect_t heterozygous_effect = mutation_block->TraitInfoForIndex(haplosome2_mutindex)[trait_index].heterozygous_effect_; + + if (f_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 <= (slim_effect_t)0.0) { // not clamped to zero, so we check here + trait_info_[trait_index].phenotype_ = 0.0; + return; + } + } + + effect_accumulator *= heterozygous_effect; + } + } + } + + trait_info_[trait_index].phenotype_ = effect_accumulator; +} + +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 b337c883..385d1b70 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() @@ -63,6 +65,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 _IndividualTraitInfo +{ + slim_effect_t phenotype_; // the phenotypic value for a trait + slim_effect_t offset_; // the individual offset combined in to produce a trait value +} IndividualTraitInfo; + class Individual : public EidosDictionaryUnretained { // This class has its copy constructor and assignment operator disabled, to prevent accidental copying. @@ -133,23 +143,30 @@ 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 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! + // 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. + 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_; @@ -161,9 +178,11 @@ 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); + 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 @@ -323,6 +342,8 @@ 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_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); @@ -330,47 +351,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. @@ -384,6 +407,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; }; @@ -402,10 +434,20 @@ 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_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; 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 8515767c..b4453da3 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. @@ -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); } } @@ -3523,8 +3523,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) @@ -3537,8 +3538,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) @@ -4304,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); @@ -4400,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); } @@ -4568,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); } @@ -4918,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; @@ -5014,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; @@ -5120,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; @@ -5161,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 { @@ -5175,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 { @@ -5194,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; } @@ -6095,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; @@ -6188,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); @@ -6217,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); } @@ -6397,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)); @@ -6467,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); @@ -6506,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 aa7cf2fe..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 @@ -469,8 +468,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/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 dfd4085c..4401b62a 100644 --- a/core/mutation.cpp +++ b/core/mutation.cpp @@ -21,7 +21,10 @@ #include "mutation.h" #include "eidos_call_signature.h" #include "eidos_property_signature.h" +#include "community.h" #include "species.h" +#include "mutation_block.h" +#include "trait.h" #include #include @@ -30,317 +33,603 @@ #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 +#pragma mark - +#pragma mark Mutation +#pragma mark - -extern std::vector gEidosValue_Object_Mutation_Registry; // this is in Eidos; see SLiM_IncreaseMutationBlockCapacity() +// A global counter used to assign all Mutation objects a unique ID +slim_mutationid_t gSLiM_next_mutation_id = 0; -void SLiM_CreateMutationBlock(void) +// 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), 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++) { - THREAD_SAFETY_IN_ANY_PARALLEL("SLiM_CreateMutationBlock(): gSLiM_Mutation_Block address change"); +#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; - // 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)); + // 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; - 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); + int trait_count = mutation_block->trait_count_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); - //std::cout << "Allocating initial mutation block, " << SLIM_MUTATION_BLOCK_INITIAL_SIZE * sizeof(Mutation) << " bytes (sizeof(Mutation) == " << sizeof(Mutation) << ")" << std::endl; + // 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. - // 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; + is_neutral_ = true; // will be set to false below as needed - *(MutationIndex *)(gSLiM_Mutation_Block + gSLiM_Mutation_Block_Capacity - 1) = -1; + // a dominance coefficient of NAN indicates independent dominance; it must be NAN for all traits + is_independent_dominance_ = std::isnan(p_dominance_coeff); - // 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()) + for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - 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); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = traits[trait_index]; + 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 + // for now we use the values passed in for trait 0, and make other traits neutral + slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : (slim_effect_t)0.0; + slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : (slim_effect_t)0.5; // can be NAN + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably + + traitInfoRec->effect_size_ = effect; + traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN + traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; + + if (effect != (slim_effect_t)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_neutral_mutations_ = false; // let the mutation type for this mutation know that it is no longer neutral + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! + + // get the realized dominance to handle the possibility of independent dominance + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + } + } + 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; + } + } } -#ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.start_critical(1); +#if DEBUG + SelfConsistencyCheck(" in Mutation::Mutation()"); #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; +#if DEBUG_MUTATIONS + std::cout << "Mutation constructed: " << this << std::endl; +#endif - // 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); +#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 - 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 + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + MutationBlock *mutation_block = species.SpeciesMutationBlock(); - 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); + // initialize the tag to the "unset" value + tag_value_ = SLIM_TAG_UNSET_VALUE; - //std::cout << "new capacity: " << gSLiM_Mutation_Block_Capacity << std::endl; + // 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; - std::uintptr_t new_mutation_block = reinterpret_cast(gSLiM_Mutation_Block); + int trait_count = mutation_block->trait_count_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForIndex(mutation_index); - // 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; + // 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. - *(MutationIndex *)(gSLiM_Mutation_Block + gSLiM_Mutation_Block_Capacity - 1) = gSLiM_Mutation_FreeIndex; + is_neutral_ = true; // will be set to false below as needed - gSLiM_Mutation_FreeIndex = old_block_capacity; + // 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)); - // Now we go out and fix Mutation * references in EidosValue_Object in all symbol tables - if (new_mutation_block != old_mutation_block) + if (mutation_type_ptr_->all_neutral_DES_) { - // 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) + // 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) { - std::uintptr_t ptr_diff = old_mutation_block - new_mutation_block; + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = traits[trait_index]; + TraitType traitType = trait->Type(); + + traitInfoRec->effect_size_ = 0.0; + traitInfoRec->dominance_coeff_UNSAFE_ = mutation_type_ptr_->DefaultDominanceForTrait(trait_index); // can be NAN + traitInfoRec->hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); - for (EidosValue_Object *mutation_value : gEidosValue_Object_Mutation_Registry) - mutation_value->PatchPointersBySubtracting(ptr_diff); + 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 + } + else + { + // 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) { - std::uintptr_t ptr_diff = new_mutation_block - old_mutation_block; + 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); // can be NAN + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); - for (EidosValue_Object *mutation_value : gEidosValue_Object_Mutation_Registry) - mutation_value->PatchPointersByAdding(ptr_diff); + traitInfoRec->effect_size_ = effect; + traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN + traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; + + if (effect != (slim_effect_t)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; // 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 + 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 * realized_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 + SelfConsistencyCheck(" in Mutation::Mutation()"); +#endif + +#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 } -void SLiM_ZeroRefcountBlock(MutationRun &p_mutation_registry, bool p_registry_only) +// 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), 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) { - THREAD_SAFETY_IN_ANY_PARALLEL("SLiM_ZeroRefcountBlock(): gSLiM_Mutation_Block change"); + Species &species = mutation_type_ptr_->species_; + const std::vector &traits = species.Traits(); + MutationBlock *mutation_block = species.SpeciesMutationBlock(); -#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 + // initialize the tag to the "unset" value + tag_value_ = SLIM_TAG_UNSET_VALUE; + + // 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; - if (p_registry_only) + int 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 + // 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 + + // 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) { - // 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(); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + Trait *trait = traits[trait_index]; + TraitType traitType = trait->Type(); - 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)); + // FIXME MULTITRAIT: This constructor needs to change to have a whole vector of trait information passed in, for effect and dominance + // for now we use the values passed in for trait 0, and make other traits neutral + slim_effect_t effect = (trait_index == 0) ? p_selection_coeff : (slim_effect_t)0.0; + slim_effect_t dominance = (trait_index == 0) ? p_dominance_coeff : (slim_effect_t)0.5; // can be NAN + slim_effect_t hemizygous_dominance = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(trait_index); // FIXME MULTITRAIT: This needs to come in from outside, probably + + traitInfoRec->effect_size_ = effect; + traitInfoRec->dominance_coeff_UNSAFE_ = dominance; // can be NAN + traitInfoRec->hemizygous_dominance_coeff_ = hemizygous_dominance; + + if (effect != (slim_effect_t)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_neutral_mutations_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT only the mutrun(s) this is added to should be recached! + + // get the realized dominance to handle the possibility of independent dominance + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + + if (traitType == TraitType::kMultiplicative) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * effect); + } + else // (traitType == TraitType::kAdditive) + { + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * effect); + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect); + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * effect); + } + } + 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 + SelfConsistencyCheck(" in Mutation::Mutation()"); +#endif + +#if DEBUG_MUTATIONS + std::cout << "Mutation constructed: " << this << std::endl; +#endif + + // Since a mutation id was supplied by the caller, we need to ensure that subsequent mutation ids generated do not collide + // 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"); + + if (gSLiM_next_mutation_id <= mutation_id_) + gSLiM_next_mutation_id = mutation_id_ + 1; } -size_t SLiMMemoryUsageForMutationBlock(void) -{ - return gSLiM_Mutation_Block_Capacity * sizeof(Mutation); -} - -size_t SLiMMemoryUsageForFreeMutations(void) +void Mutation::SelfConsistencyCheck(const std::string &p_message_end) { - size_t mut_count = 0; - MutationIndex nextFreeBlock = gSLiM_Mutation_FreeIndex; + 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; - while (nextFreeBlock != -1) + for (int trait_index = 0; trait_index < trait_count; ++trait_index) { - mut_count++; - nextFreeBlock = *(MutationIndex *)(gSLiM_Mutation_Block + nextFreeBlock); + 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_ != (slim_effect_t)0.0) + all_neutral_effects = false; } - return mut_count * sizeof(Mutation); + 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(); } -size_t SLiMMemoryUsageForMutationRefcounts(void) +slim_effect_t Mutation::RealizedDominanceForTrait(Trait *p_trait) { - return gSLiM_Mutation_Block_Capacity * sizeof(slim_refcount_t); + 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 <= (slim_effect_t)-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_; } - -#pragma mark - -#pragma mark Mutation -#pragma mark - - -// 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++) +// This should be called whenever a mutation effect is changed; it handles the necessary recaching +void Mutation::SetEffect(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_effect) { -#ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.start_critical(2); -#endif + slim_effect_t old_effect = traitInfoRec->effect_size_; - // 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_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + if (old_effect == p_new_effect) + return; - // zero out our refcount, which is now kept in a separate buffer - gSLiM_Mutation_Refcounts[BlockIndex()] = 0; + traitInfoRec->effect_size_ = p_new_effect; -#if DEBUG_MUTATIONS - std::cout << "Mutation constructed: " << this << std::endl; -#endif - -#ifdef DEBUG_LOCKS_ENABLED - gSLiM_Mutation_LOCK.end_critical(); -#endif - -#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; - -#pragma omp critical (Mutation_layout_dump) - { - if (!been_here) - { - 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_); - - 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_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_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 << std::endl; - - been_here = true; + if (p_new_effect != (slim_effect_t)0.0) + { + if (old_effect == (slim_effect_t)0.0) + { + // This mutation is no longer neutral; various observers care about that change + is_neutral_ = false; + + Species &species = mutation_type_ptr_->species_; + + species.pure_neutral_ = false; // let the sim know that it is no longer a pure-neutral simulation + mutation_type_ptr_->all_neutral_mutations_ = false; // let the mutation type for this mutation know that it is no longer pure neutral + species.nonneutral_change_counter_++; // nonneutral mutation caches need revalidation; // FIXME MULTITRAIT should have per chromosome or even narrower flags + } + + slim_effect_t realized_dominance = RealizedDominanceForTrait(p_trait); + slim_effect_t hemizygous_dominance = traitInfoRec->hemizygous_dominance_coeff_; + + // 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 + // is admittedly a philosophical issue here; if a multiplicative trait represented simply some abstract + // trait with no direct connection to fitness, then maybe clamping here would not make sense? But even + // then, negative effects don't really seem to me to make sense there, so I think this is good. + traitInfoRec->homozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_effect); // 1 + s + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * p_new_effect); // 1 + hs + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + hemizygous_dominance * p_new_effect); // 1 + hs (using h_hemi) + } + else // (p_trait->Type() == TraitType::kAdditive) + { + // For additive traits, the baseline of the trait is arbitrary and there is no cutoff. + traitInfoRec->homozygous_effect_ = (slim_effect_t)(2.0f * p_new_effect); // 2a + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * p_new_effect); // 2ha + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * hemizygous_dominance * p_new_effect); // 2ha (using h_hemi) + } + } + else // p_new_effect == 0.0; therefore, old_effect != 0.0 + { + // Changing from non-neutral to neutral; determine whether the whole mutation is now neutral + // This is a bit complicated, but I don't expect this case to be hit very often + Species &species = mutation_type_ptr_->species_; + 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) + { + if ((mut_trait_info + trait_index)->effect_size_ != (slim_effect_t)0.0) + { + is_neutral_ = false; + break; + } + } + + // 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 + + // 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; } } -#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) +// This should be called whenever a mutation dominance is changed; it handles the necessary recaching +void Mutation::SetDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) { - // initialize the tag to the "unset" value - tag_value_ = SLIM_TAG_UNSET_VALUE; + traitInfoRec->dominance_coeff_UNSAFE_ = p_new_dominance; - // 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_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + // 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; - // zero out our refcount, which is now kept in a separate buffer - gSLiM_Mutation_Refcounts[BlockIndex()] = 0; + // 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 DEBUG_MUTATIONS - std::cout << "Mutation constructed: " << this << std::endl; -#endif + slim_effect_t effect_size = traitInfoRec->effect_size_; + slim_effect_t realized_dominance = RealizedDominanceForTrait(p_trait); - // 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, - // 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"); + if (p_trait->Type() == TraitType::kMultiplicative) + { + traitInfoRec->heterozygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + realized_dominance * effect_size); + } + else // (p_trait->Type() == TraitType::kAdditive) + { + traitInfoRec->heterozygous_effect_ = (slim_effect_t)(2.0f * realized_dominance * effect_size); + } +} + +void Mutation::SetHemizygousDominance(Trait *p_trait, MutationTraitInfo *traitInfoRec, slim_effect_t p_new_dominance) +{ + traitInfoRec->hemizygous_dominance_coeff_ = p_new_dominance; - if (gSLiM_next_mutation_id <= mutation_id_) - gSLiM_next_mutation_id = mutation_id_ + 1; + // 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 (p_trait->Type() == TraitType::kMultiplicative) + { + traitInfoRec->hemizygous_effect_ = (slim_effect_t)std::max(0.0f, 1.0f + p_new_dominance * traitInfoRec->effect_size_); + } + else // (p_trait->Type() == TraitType::kAdditive) + { + traitInfoRec->hemizygous_effect_ = (slim_effect_t)(2.0f * p_new_dominance * traitInfoRec->effect_size_); + } } 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 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; } @@ -360,7 +649,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) @@ -381,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 @@ -389,8 +683,95 @@ 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_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(); + 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((double)mut_trait_info[0].effect_size_)); + 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 = mut_trait_info[trait_index].effect_size_; + + float_result->push_float_no_check((double)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. + // 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) + { + slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[0]); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)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 realized_dominance = RealizedDominanceForTrait(traits[trait_index]); + + float_result->push_float_no_check((double)realized_dominance); + } + + 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((double)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((double)dominance); + } + + return EidosValue_SP(float_result); + } + } // variables case gID_nucleotide: // ACCELERATED @@ -437,12 +818,60 @@ EidosValue_SP Mutation::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: + // Here we implement a special behavior: you can do mutation.Effect, mutation.Dominance, + // and mutation.HemizygousDominance to access a trait's values directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). + Species &species = mutation_type_ptr_->species_; + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); + + if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 6); + 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((double)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) + { + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); + + 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")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 9); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + { + // 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((double)realized_dominance)); + } + } + return super::GetProperty(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) @@ -455,8 +884,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) @@ -469,8 +899,39 @@ 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_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) 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) @@ -483,8 +944,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) @@ -508,8 +970,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) @@ -526,8 +989,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) @@ -540,8 +1004,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) @@ -554,8 +1019,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) @@ -568,8 +1034,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) @@ -586,22 +1053,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_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_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) @@ -662,13 +1116,76 @@ 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. + // 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")) + { + std::string trait_name = property_string.substr(0, property_string.length() - 6); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + { + 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! + + SetEffect(trait, traitInfoRec, new_effect); + SelfConsistencyCheck(" after setting " + property_string); + 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); + + // FIXME MULTITRAIT: finite values only! + + SetHemizygousDominance(trait, traitInfoRec, new_dominance); + SelfConsistencyCheck(" after setting " + property_string); + 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) + { + 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 + + SetDominance(trait, traitInfoRec, new_dominance); + SelfConsistencyCheck(" after setting " + property_string); + return; + } + } + return super::SetProperty(p_property_id, p_value); } } } -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); @@ -685,8 +1202,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) { @@ -708,54 +1226,126 @@ 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_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); } } -// ********************* - (void)setSelectionCoeff(float$ selectionCoeff) +// ********************* - (float)effectForTrait([Niso trait = NULL]) // -EidosValue_SP Mutation::ExecuteMethod_setSelectionCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter) +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 *selectionCoeff_value = p_arguments[0].get(); + EidosValue *trait_value = p_arguments[0].get(); - double value = selectionCoeff_value->FloatAtIndex_NOCAST(0, nullptr); - slim_selcoeff_t old_coeff = selection_coeff_; + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "effectForTrait"); - 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 + // get the trait info for this mutation + MutationBlock *mutation_block = species.SpeciesMutationBlock(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(this); - // since this selection coefficient came from the user, check and set pure_neutral_ and all_pure_neutral_DFE_ - if (selection_coeff_ != 0.0) + if (trait_indices.size() == 1) { - Species &species = mutation_type_ptr_->species_; + int64_t trait_index = trait_indices[0]; + slim_effect_t effect = mut_trait_info[trait_index].effect_size_; - 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 + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)effect)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - // 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) + for (int64_t trait_index : trait_indices) { - species.nonneutral_change_counter_++; + slim_effect_t effect = mut_trait_info[trait_index].effect_size_; + + float_result->push_float_no_check((double)effect); } + + return EidosValue_SP(float_result); } - else if (old_coeff != 0.0) // && (selection_coeff_ == 0.0) implied by the "else" +} + +// ********************* - (float)dominanceForTrait([Niso 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_; + const std::vector &traits = species.Traits(); + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "dominanceForTrait"); + + // get the trait info for this mutation + if (trait_indices.size() == 1) { - Species &species = mutation_type_ptr_->species_; + int64_t trait_index = trait_indices[0]; + Trait *trait = traits[trait_index]; + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)realized_dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); - // 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_++; + for (int64_t trait_index : trait_indices) + { + Trait *trait = traits[trait_index]; + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + + float_result->push_float_no_check((double)realized_dominance); + } + + return EidosValue_SP(float_result); } +} + +// ********************* - (float)hemizygousDominanceForTrait([Niso 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(); - // 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_hemizygousdom_sel_ = (slim_selcoeff_t)std::max(0.0, 1.0 + mutation_type_ptr_->hemizygous_dominance_coeff_ * selection_coeff_); + // get the trait indices, with bounds-checking + Species &species = mutation_type_ptr_->species_; + std::vector trait_indices; + species.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "hemizygousDominanceForTrait"); - return gStaticEidosValueVOID; + // 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((double)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((double)dominance); + } + + return EidosValue_SP(float_result); + } } // ********************* - (void)setMutationType(io$ mutType) @@ -775,13 +1365,11 @@ 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) - mutation_type_ptr_->all_pure_neutral_DFE_ = false; + if (!is_neutral_) + mutation_type_ptr_->all_neutral_mutations_ = 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 + mutation_type_ptr_->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_); + // 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; } @@ -794,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 @@ -810,13 +1398,17 @@ 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)); 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_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)); @@ -836,7 +1428,12 @@ 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_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); @@ -845,6 +1442,426 @@ 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: + 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); + } +} + +// ********************* + (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 +{ +#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(); + const std::vector &traits = species->Traits(); + + // 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_; + 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->SetEffect(traits[trait_index], traitInfoRec, 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]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + + mut->SetEffect(traits[trait_index], traitInfoRec, effect); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + for (int64_t trait_index : trait_indices) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + + mut->SetEffect(traits[trait_index], traitInfoRec, 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]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + + mut->SetEffect(traits[trait_index], traitInfoRec, 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]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t effect = static_cast(*(effects_int++)); + + mut->SetEffect(traits[trait_index], traitInfoRec, effect); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + for (int64_t trait_index : trait_indices) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t effect = static_cast(*(effects_int++)); + + mut->SetEffect(traits[trait_index], traitInfoRec, effect); + } + } + } + } + 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]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t effect = static_cast(*(effects_float++)); + + mut->SetEffect(traits[trait_index], traitInfoRec, effect); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + for (int64_t trait_index : trait_indices) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t effect = static_cast(*(effects_float++)); + + mut->SetEffect(traits[trait_index], traitInfoRec, effect); + } + } + } + } + } + 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; +} + +// ********************* + (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 +{ +#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(); + + 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_" << 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, method_name); + 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_; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + 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], traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, 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]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + for (int64_t trait_index : trait_indices) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, 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]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, 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]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t dominance = static_cast(*(dominances_int++)); + + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + for (int64_t trait_index : trait_indices) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t dominance = static_cast(*(dominances_int++)); + + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); + } + } + } + } + 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]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t dominance = static_cast(*(dominances_float++)); + + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); + } + } + else + { + for (int mutation_index = 0; mutation_index < mutations_count; ++mutation_index) + { + Mutation *mut = mutations_buffer[mutation_index]; + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(mut); + + for (int64_t trait_index : trait_indices) + { + MutationTraitInfo *traitInfoRec = mut_trait_info + trait_index; + slim_effect_t dominance = static_cast(*(dominances_float++)); + + if (p_method_id == gID_setDominanceForTrait) + mut->SetDominance(traits[trait_index], traitInfoRec, dominance); + else + mut->SetHemizygousDominance(traits[trait_index], traitInfoRec, dominance); + } + } + } + } + } + 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 755685c3..4b0fb44b 100644 --- a/core/mutation.h +++ b/core/mutation.h @@ -34,14 +34,17 @@ #include "eidos_value.h" 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; -// 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 +54,28 @@ 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. +// 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_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 + // 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 + 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 = h_hemi) +} MutationTraitInfo; typedef enum { kNewMutation = 0, // the state after new Mutation() @@ -76,11 +96,35 @@ 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_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 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 + // 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 @@ -91,15 +135,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_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 - // 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 #endif @@ -107,8 +142,17 @@ 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); + + // 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); // 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 @@ -122,7 +166,16 @@ class Mutation : public EidosDictionaryRetained virtual void SelfDelete(void) override; - inline __attribute__((always_inline)) MutationIndex BlockIndex(void) const { return (MutationIndex)(this - gSLiM_Mutation_Block); } + // 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 @@ -133,25 +186,28 @@ 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_setSelectionCoeff(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_effectForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_dominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); + EidosValue_SP ExecuteMethod_hemizygousDominanceForTrait(EidosGlobalStringID p_method_id, const std::vector &p_arguments, EidosInterpreter &p_interpreter); EidosValue_SP ExecuteMethod_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_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_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); + 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_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 @@ -182,74 +238,12 @@ class Mutation_Class : public EidosDictionaryRetained_Class virtual const std::vector *Properties(void) const override; 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; -} - + 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_block.cpp b/core/mutation_block.cpp new file mode 100644 index 00000000..81d8e8f9 --- /dev/null +++ b/core/mutation_block.cpp @@ -0,0 +1,309 @@ +// +// 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; +} + +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; + // 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..ba8db9a3 --- /dev/null +++ b/core/mutation_block.h @@ -0,0 +1,154 @@ +// +// 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); + ~MutationBlock(void); + + void IncreaseMutationBlockCapacity(void); + void ZeroRefcountBlock(MutationRun &p_mutation_registry); + + inline __attribute__((always_inline)) Mutation *MutationForIndex(MutationIndex p_index) const { return mutation_buffer_ + p_index; } + inline __attribute__((always_inline)) slim_refcount_t RefcountForIndex(MutationIndex p_index) const { return refcount_buffer_[p_index]; } + inline __attribute__((always_inline)) MutationTraitInfo *TraitInfoForIndex(MutationIndex p_index) const { return trait_info_buffer_ + (p_index * trait_count_); } + + inline __attribute__((always_inline)) 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_); + } + + 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..8fdfbeed 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,107 +444,112 @@ 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 -{ - // - // 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(); - - 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) - add_to_nonneutral_buffer(mutindex); - } -} - -void MutationRun::cache_nonneutral_mutations_REGIME_2() const -{ - // - // Regime 2 means the only mutationEffect() callbacks are (a) constant-effect, (b) neutral (i.e., - // make their mutation type become neutral), and (c) global (i.e. apply to all subpopulations). - // Here neutrality is assessed by first consulting the 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(); - - 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; - - // 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)) - add_to_nonneutral_buffer(mutindex); - } -} - -void MutationRun::cache_nonneutral_mutations_REGIME_3() 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(); - - 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; - - // 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_)) - 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 @@ -552,7 +557,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 +597,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 +620,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 +633,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 +648,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..bcca5816 100644 --- a/core/mutation_run.h +++ b/core/mutation_run.h @@ -133,7 +133,7 @@ class MutationRun // are three separate regimes in which these caches are used: // // 1. No mutationEffect() callbacks defined. Here caches depend solely upon mutation selection coefficients, - // and can be carried forward through cycles with impunity. If any mutation's selcoeff is changed between + // and can be carried forward through cycles with impunity. If any mutation's effect is changed between // zero and non-zero, a global flag in Species (nonneutral_change_counter_) marks all caches as invalid. // // 2. Only constant-effect neutral callbacks are defined: "return 0.0;". RecalculateFitness() runs through @@ -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,122 +694,122 @@ 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. - 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() const; - void cache_nonneutral_mutations_REGIME_2() const; - void cache_nonneutral_mutations_REGIME_3() 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 - { - 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(); break; - case 2: cache_nonneutral_mutations_REGIME_2(); break; - case 3: cache_nonneutral_mutations_REGIME_3(); 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 @@ -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.cpp b/core/mutation_type.cpp index 3537fec3..b6b2cedf 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 @@ -34,17 +35,17 @@ // stream output for enumerations -std::ostream& operator<<(std::ostream& p_out, DFEType p_dfe_type) +std::ostream& operator<<(std::ostream& p_out, DESType p_DES_type) { - switch (p_dfe_type) + switch (p_DES_type) { - case DFEType::kFixed: p_out << gStr_f; break; - case DFEType::kGamma: p_out << gStr_g; break; - case DFEType::kExponential: p_out << gStr_e; break; - case DFEType::kNormal: p_out << gEidosStr_n; break; - case DFEType::kWeibull: p_out << gStr_w; break; - case DFEType::kLaplace: p_out << gStr_p; break; - case DFEType::kScript: p_out << gEidosStr_s; break; + case DESType::kFixed: p_out << gStr_f; break; + case DESType::kGamma: p_out << gStr_g; break; + case DESType::kExponential: p_out << gStr_e; break; + case DESType::kNormal: p_out << gEidosStr_n; break; + case DESType::kWeibull: p_out << gStr_w; break; + case DESType::kLaplace: p_out << gStr_p; break; + case DESType::kScript: p_out << gEidosStr_s; break; } return p_out; @@ -56,12 +57,12 @@ std::ostream& operator<<(std::ostream& p_out, DFEType p_dfe_type) #pragma mark - #ifdef SLIMGUI -MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DFEType p_dfe_type, std::vector p_dfe_parameters, std::vector p_dfe_strings, int p_mutation_type_index) : +MutationType::MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings, int p_mutation_type_index) : #else -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) : +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), 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), 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 @@ -76,15 +77,30 @@ 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_DES_parameters.size() == 0) && (p_DES_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 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 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)); + // 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; + + 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; + DES_info.DES_strings_ = p_DES_strings; + + for (int 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" if (p_nuc_based) @@ -99,8 +115,8 @@ self_symbol_(EidosStringRegistry::GlobalStringIDForString(SLiMEidosScript::IDStr MutationType::~MutationType(void) { - delete cached_dfe_script_; - cached_dfe_script_ = nullptr; + delete cached_DES_script_; + cached_DES_script_ = nullptr; #ifdef SLIM_KEEP_MUTTYPE_REGISTRIES if (keeping_muttype_registry_) @@ -111,157 +127,186 @@ MutationType::~MutationType(void) #endif } -void MutationType::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) +void MutationType::ParseDESParameters(std::string &p_DES_type_string, const EidosValue_SP *const p_arguments, int p_argument_count, DESType *p_DES_type, std::vector *p_DES_parameters, std::vector *p_DES_strings) { - // First we figure out the DFE type from p_dfe_type_string, and set up expectations based on that - int expected_dfe_param_count = 0; + // First we figure out the DES type from p_DES_type_string, and set up expectations based on that + int expected_DES_param_count = 0; bool params_are_numeric = true; - if (p_dfe_type_string.compare(gStr_f) == 0) + if (p_DES_type_string.compare(gStr_f) == 0) { - *p_dfe_type = DFEType::kFixed; - expected_dfe_param_count = 1; + *p_DES_type = DESType::kFixed; + expected_DES_param_count = 1; } - else if (p_dfe_type_string.compare(gStr_g) == 0) + else if (p_DES_type_string.compare(gStr_g) == 0) { - *p_dfe_type = DFEType::kGamma; - expected_dfe_param_count = 2; + *p_DES_type = DESType::kGamma; + expected_DES_param_count = 2; } - else if (p_dfe_type_string.compare(gStr_e) == 0) + else if (p_DES_type_string.compare(gStr_e) == 0) { - *p_dfe_type = DFEType::kExponential; - expected_dfe_param_count = 1; + *p_DES_type = DESType::kExponential; + expected_DES_param_count = 1; } - else if (p_dfe_type_string.compare(gEidosStr_n) == 0) + else if (p_DES_type_string.compare(gEidosStr_n) == 0) { - *p_dfe_type = DFEType::kNormal; - expected_dfe_param_count = 2; + *p_DES_type = DESType::kNormal; + expected_DES_param_count = 2; } - else if (p_dfe_type_string.compare(gStr_w) == 0) + else if (p_DES_type_string.compare(gStr_w) == 0) { - *p_dfe_type = DFEType::kWeibull; - expected_dfe_param_count = 2; + *p_DES_type = DESType::kWeibull; + expected_DES_param_count = 2; } - else if (p_dfe_type_string.compare(gStr_p) == 0) + else if (p_DES_type_string.compare(gStr_p) == 0) { - *p_dfe_type = DFEType::kLaplace; - expected_dfe_param_count = 2; + *p_DES_type = DESType::kLaplace; + expected_DES_param_count = 2; } - else if (p_dfe_type_string.compare(gEidosStr_s) == 0) + else if (p_DES_type_string.compare(gEidosStr_s) == 0) { - *p_dfe_type = DFEType::kScript; - expected_dfe_param_count = 1; + *p_DES_type = DESType::kScript; + expected_DES_param_count = 1; params_are_numeric = false; } else - EIDOS_TERMINATION << "ERROR (MutationType::ParseDFEParameters): distribution type '" << p_dfe_type_string << "' must be 'f', 'g', 'e', 'n', 'w', or 's'." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (MutationType::ParseDESParameters): distribution type '" << p_DES_type_string << "' must be 'f', 'g', 'e', 'n', 'w', or 's'." << EidosTerminate(); - if (p_argument_count != expected_dfe_param_count) - EIDOS_TERMINATION << "ERROR (MutationType::ParseDFEParameters): distribution type '" << *p_dfe_type << "' requires exactly " << expected_dfe_param_count << " DFE parameter" << (expected_dfe_param_count == 1 ? "" : "s") << "." << EidosTerminate(); + if (p_argument_count != expected_DES_param_count) + EIDOS_TERMINATION << "ERROR (MutationType::ParseDESParameters): distribution type '" << *p_DES_type << "' requires exactly " << expected_DES_param_count << " DES parameter" << (expected_DES_param_count == 1 ? "" : "s") << "." << EidosTerminate(); // Next we extract the parameter values, checking their types in accordance with params_are_numeric - p_dfe_parameters->clear(); - p_dfe_strings->clear(); + p_DES_parameters->clear(); + p_DES_strings->clear(); - for (int dfe_param_index = 0; dfe_param_index < expected_dfe_param_count; ++dfe_param_index) + for (int DES_param_index = 0; DES_param_index < expected_DES_param_count; ++DES_param_index) { - EidosValue *dfe_param_value = p_arguments[dfe_param_index].get(); - EidosValueType dfe_param_type = dfe_param_value->Type(); + EidosValue *DES_param_value = p_arguments[DES_param_index].get(); + EidosValueType DES_param_type = DES_param_value->Type(); if (params_are_numeric) { - if ((dfe_param_type != EidosValueType::kValueFloat) && (dfe_param_type != EidosValueType::kValueInt)) - EIDOS_TERMINATION << "ERROR (MutationType::ParseDFEParameters): the parameters for a DFE of type '" << *p_dfe_type << "' must be of type numeric (integer or float)." << EidosTerminate(); + if ((DES_param_type != EidosValueType::kValueFloat) && (DES_param_type != EidosValueType::kValueInt)) + EIDOS_TERMINATION << "ERROR (MutationType::ParseDESParameters): the parameters for a DES of type '" << *p_DES_type << "' must be of type numeric (integer or float)." << EidosTerminate(); - p_dfe_parameters->emplace_back(dfe_param_value->NumericAtIndex_NOCAST(0, nullptr)); + p_DES_parameters->emplace_back(DES_param_value->NumericAtIndex_NOCAST(0, nullptr)); } else { - if (dfe_param_type != EidosValueType::kValueString) - EIDOS_TERMINATION << "ERROR (MutationType::ParseDFEParameters): the parameters for a DFE of type '" << *p_dfe_type << "' must be of type string." << EidosTerminate(); + if (DES_param_type != EidosValueType::kValueString) + EIDOS_TERMINATION << "ERROR (MutationType::ParseDESParameters): the parameters for a DES of type '" << *p_DES_type << "' must be of type string." << EidosTerminate(); - p_dfe_strings->emplace_back(dfe_param_value->StringAtIndex_NOCAST(0, nullptr)); + p_DES_strings->emplace_back(DES_param_value->StringAtIndex_NOCAST(0, nullptr)); } } - // Finally, we bounds-check the DFE parameters in the cases where there is a hard bound - switch (*p_dfe_type) + // Finally, we bounds-check the DES parameters in the cases where there is a hard bound + switch (*p_DES_type) { - case DFEType::kFixed: - // no limits on fixed DFEs; we could check that s >= -1, but that assumes that the selection coefficients are being used as selection coefficients + case DESType::kFixed: + // no limits on fixed DESs; we could check that s >= -1, but that assumes that the selection coefficients are being used as selection coefficients break; - case DFEType::kGamma: + case DESType::kGamma: // mean is unrestricted, shape parameter must be >0 (officially mean > 0, but we allow mean <= 0 and the GSL handles it) - if ((*p_dfe_parameters)[1] <= 0.0) - EIDOS_TERMINATION << "ERROR (MutationType::ParseDFEParameters): a DFE of type 'g' must have a shape parameter > 0." << EidosTerminate(); + if ((*p_DES_parameters)[1] <= 0.0) + EIDOS_TERMINATION << "ERROR (MutationType::ParseDESParameters): a DES of type 'g' must have a shape parameter > 0." << EidosTerminate(); break; - case DFEType::kExponential: - // no limits on exponential DFEs (officially scale > 0, but we allow scale <= 0 and the GSL handles it) + case DESType::kExponential: + // no limits on exponential DESs (officially scale > 0, but we allow scale <= 0 and the GSL handles it) break; - case DFEType::kNormal: + case DESType::kNormal: // mean is unrestricted, sd parameter must be >= 0 - if ((*p_dfe_parameters)[1] < 0.0) - EIDOS_TERMINATION << "ERROR (MutationType::ParseDFEParameters): a DFE of type 'n' must have a standard deviation parameter >= 0." << EidosTerminate(); + if ((*p_DES_parameters)[1] < 0.0) + EIDOS_TERMINATION << "ERROR (MutationType::ParseDESParameters): a DES of type 'n' must have a standard deviation parameter >= 0." << EidosTerminate(); break; - case DFEType::kWeibull: + case DESType::kWeibull: // scale and shape must both be > 0 - if ((*p_dfe_parameters)[0] <= 0.0) - EIDOS_TERMINATION << "ERROR (MutationType::ParseDFEParameters): a DFE of type 'w' must have a scale parameter > 0." << EidosTerminate(); - if ((*p_dfe_parameters)[1] <= 0.0) - EIDOS_TERMINATION << "ERROR (MutationType::ParseDFEParameters): a DFE of type 'w' must have a shape parameter > 0." << EidosTerminate(); + if ((*p_DES_parameters)[0] <= 0.0) + EIDOS_TERMINATION << "ERROR (MutationType::ParseDESParameters): a DES of type 'w' must have a scale parameter > 0." << EidosTerminate(); + if ((*p_DES_parameters)[1] <= 0.0) + EIDOS_TERMINATION << "ERROR (MutationType::ParseDESParameters): a DES of type 'w' must have a shape parameter > 0." << EidosTerminate(); break; - case DFEType::kLaplace: + case DESType::kLaplace: // mean is unrestricted, scale parameter must be > 0 - if ((*p_dfe_parameters)[1] <= 0.0) - EIDOS_TERMINATION << "ERROR (MutationType::ParseDFEParameters): a DFE of type 'p' must have a scale parameter > 0." << EidosTerminate(); + if ((*p_DES_parameters)[1] <= 0.0) + EIDOS_TERMINATION << "ERROR (MutationType::ParseDESParameters): a DES of type 'p' must have a scale parameter > 0." << EidosTerminate(); break; - case DFEType::kScript: + case DESType::kScript: // no limits on script here; the script is checked when it gets tokenized/parsed/executed break; } } -double MutationType::DrawSelectionCoefficient(void) const +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]; + // 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 (DES_info.DES_type_) { - case DFEType::kFixed: return dfe_parameters_[0]; + case DESType::kFixed: return static_cast(DES_info.DES_parameters_[0]); - case DFEType::kGamma: + case DESType::kGamma: { gsl_rng *rng_gsl = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_gamma(rng_gsl, dfe_parameters_[1], dfe_parameters_[0] / dfe_parameters_[1]); + return static_cast(gsl_ran_gamma(rng_gsl, DES_info.DES_parameters_[1], DES_info.DES_parameters_[0] / DES_info.DES_parameters_[1])); } - case DFEType::kExponential: + case DESType::kExponential: { gsl_rng *rng_gsl = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_exponential(rng_gsl, dfe_parameters_[0]); + return static_cast(gsl_ran_exponential(rng_gsl, DES_info.DES_parameters_[0])); } - case DFEType::kNormal: + case DESType::kNormal: { gsl_rng *rng_gsl = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_gaussian(rng_gsl, dfe_parameters_[1]) + dfe_parameters_[0]; + return static_cast(gsl_ran_gaussian(rng_gsl, DES_info.DES_parameters_[1]) + DES_info.DES_parameters_[0]); } - case DFEType::kWeibull: + case DESType::kWeibull: { gsl_rng *rng_gsl = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_weibull(rng_gsl, dfe_parameters_[0], dfe_parameters_[1]); + return static_cast(gsl_ran_weibull(rng_gsl, DES_info.DES_parameters_[0], DES_info.DES_parameters_[1])); } - case DFEType::kLaplace: + case DESType::kLaplace: { gsl_rng *rng_gsl = EIDOS_GSL_RNG(omp_get_thread_num()); - return gsl_ran_laplace(rng_gsl, dfe_parameters_[1]) + dfe_parameters_[0]; + return static_cast(gsl_ran_laplace(rng_gsl, DES_info.DES_parameters_[1]) + DES_info.DES_parameters_[0]); } - case DFEType::kScript: + case DESType::kScript: { // We have a script string that we need to execute, and it will return a float or integer to us. This // is basically a lambda call, so the code here is parallel to the executeLambda() code in many ways. @@ -269,9 +314,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; @@ -284,46 +329,46 @@ double MutationType::DrawSelectionCoefficient(void) const EidosErrorContext error_context_save = gEidosErrorContext; // We try to do tokenization and parsing once per script, by caching the script - if (!cached_dfe_script_) + if (!cached_DES_script_) { - std::string script_string = dfe_strings_[0]; - cached_dfe_script_ = new EidosScript(script_string); + std::string script_string = DES_info.DES_strings_[0]; + cached_DES_script_ = new EidosScript(script_string); - gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, cached_dfe_script_}; + gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, cached_DES_script_}; try { - cached_dfe_script_->Tokenize(); - cached_dfe_script_->ParseInterpreterBlockToAST(false); + cached_DES_script_->Tokenize(); + cached_DES_script_->ParseInterpreterBlockToAST(false); } catch (...) { if (gEidosTerminateThrows) { gEidosErrorContext = error_context_save; - TranslateErrorContextToUserScript("DrawSelectionCoefficient()"); + TranslateErrorContextToUserScript("DrawEffectForTrait()"); } - delete cached_dfe_script_; - cached_dfe_script_ = nullptr; + delete cached_DES_script_; + cached_DES_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' DES callback script." << EidosTerminate(nullptr); } } // Execute inside try/catch so we can handle errors well - gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, cached_dfe_script_}; + gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, cached_DES_script_}; try { Community &community = species_.community_; EidosSymbolTable client_symbols(EidosSymbolTableType::kLocalVariablesTable, &community.SymbolTable()); EidosFunctionMap &function_map = community.FunctionMap(); - EidosInterpreter interpreter(*cached_dfe_script_, client_symbols, function_map, &community, SLIM_OUTSTREAM, SLIM_ERRSTREAM + EidosInterpreter interpreter(*cached_DES_script_, client_symbols, function_map, &community, SLIM_OUTSTREAM, SLIM_ERRSTREAM #ifdef SLIMGUI , community.check_infinite_loops_ #endif @@ -339,7 +384,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' DES callbacks must provide a singleton float or integer return value." << EidosTerminate(nullptr); } catch (...) { @@ -354,12 +399,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; @@ -369,37 +414,38 @@ 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; + return static_cast(sel_coeff); } } - EIDOS_TERMINATION << "ERROR (MutationType::DrawSelectionCoefficient): (internal error) unexpected dfe_type_ value." << EidosTerminate(); + EIDOS_TERMINATION << "ERROR (MutationType::DrawEffectForTrait): (internal error) unexpected DES_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{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_ << ", DES_type_ '" << p_mutation_type.des_type_ << "', DES_parameters_ <"; - if (p_mutation_type.dfe_parameters_.size() > 0) + if (p_mutation_type.des_parameters_.size() > 0) { - for (unsigned int i = 0; i < p_mutation_type.dfe_parameters_.size(); ++i) + for (unsigned int i = 0; i < p_mutation_type.des_parameters_.size(); ++i) { - p_outstream << p_mutation_type.dfe_parameters_[i]; + p_outstream << p_mutation_type.des_parameters_[i]; - if (i < p_mutation_type.dfe_parameters_.size() - 1) + if (i < p_mutation_type.des_parameters_.size() - 1) p_outstream << " "; } } else { - for (unsigned int i = 0; i < p_mutation_type.dfe_strings_.size(); ++i) + for (unsigned int i = 0; i < p_mutation_type.des_strings_.size(); ++i) { - p_outstream << "\"" << p_mutation_type.dfe_strings_[i] << "\""; + p_outstream << "\"" << p_mutation_type.des_strings_[i] << "\""; - if (i < p_mutation_type.dfe_strings_.size() - 1) + if (i < p_mutation_type.des_strings_.size() - 1) p_outstream << " "; } } @@ -407,7 +453,7 @@ std::ostream &operator<<(std::ostream &p_outstream, const MutationType &p_mutati p_outstream << ">}"; return p_outstream; -} +}*/ // @@ -439,49 +485,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)); @@ -494,10 +497,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(dominance_coeff_)); - 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: @@ -544,8 +543,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) @@ -558,8 +558,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) @@ -576,20 +577,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->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 @@ -619,34 +606,6 @@ void MutationType::SetProperty(EidosGlobalStringID p_property_id, const EidosVal return; } - case gID_dominanceCoeff: - { - double value = p_value.FloatAtIndex_NOCAST(0, nullptr); - - 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; - species_.community_.mutation_types_changed_ = true; - - 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 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; - species_.community_.mutation_types_changed_ = true; - - return; - } - case gID_mutationStackGroup: { int64_t new_group = p_value.IntAtIndex_NOCAST(0, nullptr); @@ -695,8 +654,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); @@ -713,8 +673,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) { @@ -736,65 +697,358 @@ 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_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); + } +} + +// ********************* - (float$)defaultDominanceForTrait([Niso trait = NULL]) +// +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]; + + 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((double)DefaultDominanceForTrait(trait_index)); + + return EidosValue_SP(float_result); + } +} + +// ********************* - (float$)defaultHemizygousDominanceForTrait([Niso 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((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((double)DefaultHemizygousDominanceForTrait(trait_index)); + + return EidosValue_SP(float_result); } } -// ********************* - (float)drawSelectionCoefficient([integer$ n = 1]) +// ********************* - (fs)effectDistributionParamsForTrait([Niso trait = NULL]) // -EidosValue_SP MutationType::ExecuteMethod_drawSelectionCoefficient(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, "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) + { + EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + + if (DES_info.DES_parameters_.size() > 0) + is_float = true; + else + is_string = true; + } + + if (is_float && is_string) + EIDOS_TERMINATION << "ERROR (ExecuteMethod_effectDistributionParamsForTrait): effectDistributionParamsForTrait() requires all specified traits to have either float or string parameters (not a mixture) for their distributions of effects." << EidosTerminate(nullptr); + + if (is_float) + { + EidosValue_Float *float_result = new (gEidosValuePool->AllocateChunk()) EidosValue_Float(); + + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + + for (double param : DES_info.DES_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 &DES_info = effect_distributions_[trait_index]; + + for (const std::string ¶m : DES_info.DES_strings_) + string_result->PushString(param); + } + + return EidosValue_SP(string_result); + } +} + +// ********************* - (string$)effectDistributionTypeForTrait([Niso trait = NULL]) +// +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, "effectDistributionTypeForTrait"); + + // assemble the result + EidosValue_String *string_result = new (gEidosValuePool->AllocateChunk()) EidosValue_String(); + + for (int64_t trait_index : trait_indices) + { + EffectDistributionInfo &DES_info = effect_distributions_[trait_index]; + + switch (DES_info.DES_type_) + { + case DESType::kFixed: string_result->PushString(gStr_f); break; + case DESType::kGamma: string_result->PushString(gStr_g); break; + case DESType::kExponential: string_result->PushString(gStr_e); break; + case DESType::kNormal: string_result->PushString(gEidosStr_n); break; + case DESType::kWeibull: string_result->PushString(gStr_w); break; + case DESType::kLaplace: string_result->PushString(gStr_p); break; + case DESType::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([Niso 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, "drawEffectForTrait"); + + // 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); + + if ((trait_indices.size() == 1) && (num_draws == 1)) + { + int64_t trait_index = trait_indices[0]; + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)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((double)DrawEffectForTrait(trait_index)); + + return EidosValue_SP(float_result); + } +} + +// ********************* - (void)setDefaultDominanceForTrait(Niso 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(); + + // get the trait indices, with bounds-checking + std::vector trait_indices; + species_.GetTraitIndicesFromEidosValue(trait_indices, trait_value, "setDefaultDominanceForTrait"); + + 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_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_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_.community_.mutation_types_changed_ = true; + + SelfConsistencyCheck(" in setDefaultDominanceForTrait()"); + + return gStaticEidosValueVOID; +} + +// ********************* - (void)setDefaultHemizygousDominanceForTrait(Niso 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); - EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->resize_no_initialize(num_draws); - result_SP = EidosValue_SP(float_result); + // 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; - for (int64_t draw_index = 0; draw_index < num_draws; ++draw_index) - float_result->set_float_no_check(DrawSelectionCoefficient(), draw_index); + SelfConsistencyCheck(" in setDefaultDominanceForTrait()"); - return result_SP; + return gStaticEidosValueVOID; } -// ********************* - (void)setDistribution(string$ distributionType, ...) +// ********************* - (void)setEffectDistributionForTrait(Niso trait, string$ distributionType, ...) // -EidosValue_SP MutationType::ExecuteMethod_setDistribution(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 *distributionType_value = p_arguments[0].get(); - std::string dfe_type_string = distributionType_value->StringAtIndex_NOCAST(0, nullptr); + EidosValue *trait_value = p_arguments[0].get(); + EidosValue *distributionType_value = p_arguments[1].get(); + std::string DES_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, "setEffectDistributionForTrait"); - // Parse the DFE type and parameters, and do various sanity checks - DFEType dfe_type; - std::vector dfe_parameters; - std::vector dfe_strings; + // Parse the DES type and parameters, and do various sanity checks + DESType DES_type; + std::vector DES_parameters; + std::vector DES_strings; - MutationType::ParseDFEParameters(dfe_type_string, p_arguments.data() + 1, (int)p_arguments.size() - 1, &dfe_type, &dfe_parameters, &dfe_strings); + MutationType::ParseDESParameters(DES_type_string, p_arguments.data() + 2, (int)p_arguments.size() - 2, &DES_type, &DES_parameters, &DES_strings); - // keep track of whether we have ever seen a type 's' (scripted) DFE; if so, we switch to a slower case when evolving - if (dfe_type == DFEType::kScript) - species_.type_s_dfes_present_ = true; + // keep track of whether we have ever seen a type 's' (scripted) DES; if so, we switch to a slower case when evolving + if (DES_type == DESType::kScript) + species_.type_s_DESs_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 &DES_info = effect_distributions_[trait_index]; + + DES_info.DES_type_ = DES_type; + DES_info.DES_parameters_ = DES_parameters; + DES_info.DES_strings_ = DES_strings; + } // mark that mutation types changed, so they get redisplayed in SLiMgui species_.community_.mutation_types_changed_ = true; - // check whether 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)) + // 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_DFE_ = false; + all_neutral_mutations_ = false; } return gStaticEidosValueVOID; @@ -808,7 +1062,7 @@ EidosValue_SP MutationType::ExecuteMethod_setDistribution(EidosGlobalStringID p_ #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 @@ -823,10 +1077,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))); properties->emplace_back((EidosPropertySignature *)(new EidosPropertySignature(gStr_nucleotideBased, true, kEidosValueMaskLogical | kEidosValueMaskSingleton))); @@ -851,8 +1101,14 @@ 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))->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); } @@ -920,6 +1176,5 @@ const std::vector *MutationType_Class::Methods(void) c - diff --git a/core/mutation_type.h b/core/mutation_type.h index 3c02ce9c..8e71bf25 100644 --- a/core/mutation_type.h +++ b/core/mutation_type.h @@ -20,8 +20,8 @@ /* The class MutationType represents a type of mutation defined in the input file, such as a synonymous mutation or an adaptive mutation. - A particular mutation type is defined by its distribution of fitness effects (DFE) and its dominance coefficient. Once a mutation type - is defined, a draw from its DFE can be generated to determine the selection coefficient of a particular mutation of that type. + A particular mutation type is defined by its distribution of effect sizes (DES) and its dominance coefficient. Once a mutation type + is defined, a draw from its DES can be generated to determine the selection coefficient of a particular mutation of that type. */ @@ -41,13 +41,15 @@ #include "slim_globals.h" 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 fitness effects (DFE) that a mutation type can draw from -enum class DFEType : char { +// This enumeration represents a type of distribution of effect sizes (DES) that a mutation type can draw from +enum class DESType : char { kFixed = 0, kGamma, kExponential, @@ -57,9 +59,21 @@ enum class DFEType : char { kScript }; -std::ostream& operator<<(std::ostream& p_out, DFEType p_dfe_type); +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_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) +} EffectDistributionInfo; + + class MutationType : public EidosDictionaryUnretained { // This class has its copy constructor and assignment operator disabled, to prevent accidental copying. @@ -72,25 +86,21 @@ 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) // // 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 - slim_selcoeff_t dominance_coeff_; // dominance coefficient (h) - 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) - 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) + 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) @@ -105,7 +115,7 @@ class MutationType : public EidosDictionaryUnretained slim_usertag_t tag_value_ = SLIM_TAG_UNSET_VALUE; // a user-defined tag value - mutable EidosScript *cached_dfe_script_; // used by DFE type 's' to hold a cached script for the DFE + 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, @@ -120,18 +130,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 DFE, 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_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_DFE_ is true if the DFE 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_DFE_; + // 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_DFE_ 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 @@ -148,7 +166,7 @@ class MutationType : public EidosDictionaryUnretained // subject_to_mutationEffect_callback_ is set by RecalculateFitness() if the muttype is currently influenced by a callback in any subpop. // Mutations with this flag set are considered to be non-neutral, since their fitness value is unpredictable; mutations without - // this flag set, on the other hand, are not influenced by any callback (active or inactive), so their selcoeff may be consulted. + // this flag set, on the other hand, are not influenced by any callback (active or inactive), so their effect may be consulted. // This flag is valid only when the "nonneutral regime" (i.e., sim.last_nonneutral_regime_) is 3 (non-constant or non-neutral // callbacks present); it is not valid in other scenarios, so it should be used with extreme caution. mutable bool subject_to_mutationEffect_callback_ = false; @@ -163,16 +181,34 @@ class MutationType : public EidosDictionaryUnretained MutationType& operator=(const MutationType&) = delete; // no copying MutationType(void) = delete; // no null construction #ifdef SLIMGUI - MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DFEType p_dfe_type, std::vector p_dfe_parameters, std::vector p_dfe_strings, int p_mutation_type_index); + MutationType(Species &p_species, slim_objectid_t p_mutation_type_id, double p_dominance_coeff, bool p_nuc_based, DESType p_DES_type, std::vector p_DES_parameters, std::vector p_DES_strings, int p_mutation_type_index); #else - 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); + 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 ~MutationType(void); - 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); + 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]; + + 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 - double DrawSelectionCoefficient(void) const; // draw a selection coefficient from this mutation type's DFE // // Eidos support @@ -186,16 +222,21 @@ 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_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 - 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 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/polymorphism.cpp b/core/polymorphism.cpp index 975bb14e..0c61f557 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 @@ -38,20 +39,46 @@ void Polymorphism::Print_ID_Tag(std::ostream &p_out) const // mutation_ptr_->tag_value_ added at the end // Added mutation_ptr_->mutation_id_ to this output, BCH 11 June 2016 - // Switched to full-precision output of selcoeff and domcoeff, for accurate reloading; BCH 22 March 2019 + // Switched to full-precision output of effect and domcoeff, for accurate reloading; BCH 22 March 2019 THREAD_SAFETY_IN_ACTIVE_PARALLEL("Polymorphism::Print_ID_Tag(): usage of statics"); static char double_buf[40]; 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, (double)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_->mutation_type_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 << ","; + + 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, (double)dominance); // necessary precision for non-lossiness + p_out << double_buf; + } + } p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -73,20 +100,46 @@ void Polymorphism::Print_ID_Tag(std::ostream &p_out) const void Polymorphism::Print_ID(std::ostream &p_out) const { // Added mutation_ptr_->mutation_id_ to this output, BCH 11 June 2016 - // Switched to full-precision output of selcoeff and domcoeff, for accurate reloading; BCH 22 March 2019 + // Switched to full-precision output of effect and domcoeff, for accurate reloading; BCH 22 March 2019 THREAD_SAFETY_IN_ACTIVE_PARALLEL("Polymorphism::Print_ID(): usage of statics"); static char double_buf[40]; 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, (double)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_->mutation_type_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 << ","; + + 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, (double)dominance); // necessary precision for non-lossiness + p_out << double_buf; + } + } p_out << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; @@ -100,7 +153,7 @@ void Polymorphism::Print_ID(std::ostream &p_out) const void Polymorphism::Print_NoID_Tag(std::ostream &p_out) const { // Added mutation_ptr_->mutation_id_ to this output, BCH 11 June 2016 - // Note that Print_ID() now outputs selcoeff and domcoeff in full precision, whereas here we do not; BCH 22 March 2019 + // Note that Print_ID() now outputs effect and domcoeff in full precision, whereas here we do not; BCH 22 March 2019 p_out << mutation_ptr_->mutation_id_ << " " << "m" << mutation_ptr_->mutation_type_ptr_->mutation_type_id_ << " " << mutation_ptr_->position_; // BCH 2/2/2025: Note that in multi-chrom models, this method now prints the chromosome symbol after the position @@ -115,8 +168,39 @@ 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 << ","; + + 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 - 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 << " p" << mutation_ptr_->subpop_index_ << " " << mutation_ptr_->origin_tick_ << " " << prevalence_; // output a nucleotide if available if (mutation_ptr_->mutation_type_ptr_->nucleotide_based_) @@ -136,7 +220,7 @@ void Polymorphism::Print_NoID_Tag(std::ostream &p_out) const void Polymorphism::Print_NoID(std::ostream &p_out) const { // Added mutation_ptr_->mutation_id_ to this output, BCH 11 June 2016 - // Note that Print_ID() now outputs selcoeff and domcoeff in full precision, whereas here we do not; BCH 22 March 2019 + // Note that Print_ID() now outputs effect and domcoeff in full precision, whereas here we do not; BCH 22 March 2019 p_out << mutation_ptr_->mutation_id_ << " " << "m" << mutation_ptr_->mutation_type_ptr_->mutation_type_id_ << " " << mutation_ptr_->position_; // BCH 2/2/2025: Note that in multi-chrom models, this method now prints the chromosome symbol after the position @@ -151,8 +235,39 @@ 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 << ","; + + 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 - 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 << " 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 b026ba5f..65f31dab 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 @@ -706,17 +711,23 @@ 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; + // 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) { if (mate_choice_callback->block_active_) @@ -746,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); @@ -801,8 +816,85 @@ slim_popsize_t Population::ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_ind if (mate_choice_callback->contains_weights_) { - local_weights_ptr = EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(current_weights, weights_length)); - callback_symbols.InitializeConstantSymbolEntry(gEidosID_weights, local_weights_ptr); + // we need an EidosValue for the `weights` pseudo-parameter; these are several ways to get it + if (returned_weights) + { + // 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_; + + 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_) + { + local_weights = p_source_subpop->mate_choice_weights_; + } + else + { + 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); + } + + 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 + { + // 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_; + } + } + + // 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; + + weights_reflect_chosen_mate = false; + } + + callback_symbols.InitializeConstantSymbolEntry(gEidosID_weights, local_weights); } try @@ -813,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 @@ -830,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 @@ -851,21 +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 - 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; @@ -889,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) @@ -909,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_) @@ -929,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_); @@ -961,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) @@ -980,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; @@ -995,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) { @@ -1018,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) { @@ -1030,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; @@ -1057,7 +1139,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)); @@ -1197,7 +1280,7 @@ bool Population::ApplyModifyChildCallbacks(Individual *p_child, Individual *p_pa // WF only: // generate children for subpopulation p_subpop_id, drawing from all source populations, handling crossover and mutation -void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice_callbacks_present, bool p_modify_child_callbacks_present, bool p_recombination_callbacks_present, bool p_mutation_callbacks_present, bool p_type_s_dfe_present) +void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice_callbacks_present, bool p_modify_child_callbacks_present, bool p_recombination_callbacks_present, bool p_mutation_callbacks_present, bool p_type_s_DES_present) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::EvolveSubpopulation(): usage of statics, probably many other issues"); @@ -1724,7 +1807,7 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice if (species_.DoingAnyMutationRunExperiments() && (species_.Chromosomes().size() == 1)) species_.Chromosomes()[0]->StartMutationRunExperimentClock(); - if (p_mate_choice_callbacks_present || p_modify_child_callbacks_present || p_recombination_callbacks_present || p_mutation_callbacks_present || p_type_s_dfe_present) + if (p_mate_choice_callbacks_present || p_modify_child_callbacks_present || p_recombination_callbacks_present || p_mutation_callbacks_present || p_type_s_DES_present) { // CALLBACKS PRESENT: We need to generate offspring in a randomized order. This way the callbacks are presented with potential offspring // a random order, and so it is much easier to write a callback that runs for less than the full offspring generation phase (influencing a @@ -2998,7 +3081,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_; @@ -3161,14 +3244,14 @@ 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_DFE_ 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 } } } catch (...) { // DrawNewMutation() / DrawNewMutationExtended() can raise, but it is (presumably) rare; we can leak mutations here - // It occurs primarily with type 's' DFEs; an error in the user's script can cause a raise through here. + // It occurs primarily with type 's' DESs; an error in the user's script can cause a raise through here. #ifdef _OPENMP saw_error_in_critical = true; // can't throw from a critical region, even when not inside a parallel region! #else @@ -3185,7 +3268,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(); @@ -3255,7 +3338,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); @@ -3273,7 +3356,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_)); } } } @@ -3405,7 +3488,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); @@ -3423,7 +3506,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_)); } } } @@ -3459,7 +3542,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); @@ -3477,7 +3560,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_)); } } } @@ -3614,7 +3697,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); @@ -3632,7 +3715,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_)); } } } @@ -3803,14 +3886,14 @@ 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_DFE_ 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 } } } catch (...) { // DrawNewMutation() / DrawNewMutationExtended() can raise, but it is (presumably) rare; we can leak mutations here - // It occurs primarily with type 's' DFEs; an error in the user's script can cause a raise through here. + // It occurs primarily with type 's' DESs; an error in the user's script can cause a raise through here. #ifdef _OPENMP saw_error_in_critical = true; // can't throw from a critical region, even when not inside a parallel region! #else @@ -3836,7 +3919,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_; @@ -3885,7 +3968,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); @@ -3903,7 +3986,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)); } } } @@ -4047,7 +4130,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_; @@ -4205,14 +4288,14 @@ 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_DFE_ 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 } } } catch (...) { // DrawNewMutation() / DrawNewMutationExtended() can raise, but it is (presumably) rare; we can leak mutations here - // It occurs primarily with type 's' DFEs; an error in the user's script can cause a raise through here. + // It occurs primarily with type 's' DESs; an error in the user's script can cause a raise through here. #ifdef _OPENMP saw_error_in_critical = true; // can't throw from a critical region, even when not inside a parallel region! #else @@ -4229,7 +4312,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(); @@ -4344,7 +4427,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); @@ -4362,7 +4445,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_)); } } } @@ -4398,7 +4481,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); @@ -4416,7 +4499,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_)); } } } @@ -4553,7 +4636,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); @@ -4571,7 +4654,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_)); } } } @@ -4900,12 +4983,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; @@ -5006,7 +5090,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)); } } } @@ -5040,7 +5124,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) @@ -5051,7 +5135,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; @@ -5134,7 +5218,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; @@ -5190,34 +5274,13 @@ void Population::AddTallyForMutationTypeAndBinNumber(int p_mutation_type_index, } #endif -void Population::ValidateMutationFitnessCaches(void) -{ - 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; - - while (registry_iter != registry_iter_end) - { - 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 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); - } -} - -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 // 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) @@ -5388,19 +5451,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, p_force_trait_recalculation); } else { @@ -5428,7 +5499,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, p_force_trait_recalculation); } } @@ -5450,6 +5521,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 @@ -5757,6 +5833,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_) @@ -5792,7 +5870,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; @@ -5817,7 +5895,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; @@ -6067,6 +6145,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) { @@ -6135,7 +6246,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) @@ -6947,8 +7058,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; @@ -6982,7 +7094,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 @@ -7019,7 +7132,7 @@ void Population::TallyMutationReferencesAcrossSubpopulations(std::vector 1); + mutation_block->ZeroRefcountBlock(mutation_registry_); for (Chromosome *chromosome : species_.Chromosomes()) chromosome->tallied_haplosome_count_ = 0; @@ -7088,7 +7201,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 @@ -7114,7 +7228,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; @@ -7158,7 +7272,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()) { @@ -7175,7 +7292,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) { @@ -7241,7 +7357,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) @@ -7268,8 +7386,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]; @@ -7289,7 +7405,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 @@ -7323,7 +7441,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; @@ -7336,7 +7454,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); @@ -7344,7 +7461,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_]; @@ -7358,7 +7475,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); @@ -7366,7 +7482,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); } } @@ -7376,7 +7492,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 @@ -7410,7 +7528,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_]; @@ -7423,7 +7541,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); @@ -7431,7 +7548,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); @@ -7483,8 +7600,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; @@ -7681,7 +7799,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); } } } @@ -7760,7 +7878,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 @@ -7780,7 +7898,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(); @@ -7826,7 +7944,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(); @@ -7909,7 +8027,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 @@ -7922,7 +8040,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 @@ -8098,7 +8216,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 { @@ -8138,14 +8256,19 @@ 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_selcoeff_t selection_coeff = mutation_ptr->selection_coeff_; - slim_selcoeff_t dominance_coeff = mutation_type_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_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 slim_objectid_t subpop_index = mutation_ptr->subpop_index_; slim_tick_t origin_tick = mutation_ptr->origin_tick_; slim_refcount_t prevalence = polymorphism.prevalence_; @@ -8300,8 +8423,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_selcoeff_t selection_coeff = substitution_ptr->selection_coeff_; - slim_selcoeff_t dominance_coeff = mutation_type_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_UNSAFE_; // can be NAN + 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_; @@ -8388,7 +8514,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 @@ -8442,7 +8568,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..64c49a18 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); @@ -250,11 +220,8 @@ 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); + 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); @@ -336,7 +303,7 @@ class Population slim_popsize_t ApplyMateChoiceCallbacks(slim_popsize_t p_parent1_index, Subpopulation *p_subpop, Subpopulation *p_source_subpop, std::vector &p_mate_choice_callbacks); // generate children for subpopulation p_subpop_id, drawing from all source populations, handling crossover and mutation - void EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice_callbacks_present, bool p_modify_child_callbacks_present, bool p_recombination_callbacks_present, bool p_mutation_callbacks_present, bool p_type_s_dfe_present); + void EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice_callbacks_present, bool p_modify_child_callbacks_present, bool p_recombination_callbacks_present, bool p_mutation_callbacks_present, bool p_type_s_DES_present); // step forward a generation: make the children become the parents void SwapGenerations(void); diff --git a/core/slim_eidos_block.cpp b/core/slim_eidos_block.cpp index 4306887d..7f5a9405 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; } @@ -1777,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 @@ -1807,6 +1811,9 @@ const std::vector *SLiMEidosBlock_Class::Properties( } +#ifdef EIDOS_GUI +// SLiMTypeTable and SLiMTypeInterpreter are only used in SLiMgui and QtSLiM + // // SLiMTypeTable // @@ -1996,6 +2003,41 @@ 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 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_; + 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); + } + } return ret; } @@ -2053,6 +2095,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..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. @@ -156,6 +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_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 @@ -255,6 +258,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 +316,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/slim_functions.cpp b/core/slim_functions.cpp index 98bbbf02..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 @@ -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) @@ -721,7 +724,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.dominance; // calculate number of haploid lethal equivalents (B or inbreeding load) // this equation is from Morton et al. 1956 @@ -1015,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): "; @@ -1052,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 = "*"]) diff --git a/core/slim_globals.cpp b/core/slim_globals.cpp index aa08e6fe..19f1e5e4 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); @@ -97,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(); - // Make sure the Eidos context information has already been configured; this has to be done first thing, // so that any customizations to Eidos that SLiM introduces take effect before anything else happens if (gEidosContextVersion == 0.0) @@ -519,6 +518,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 +596,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; @@ -1180,6 +1181,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); @@ -1193,6 +1195,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); @@ -1239,19 +1245,22 @@ 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); 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_distributionType = EidosRegisteredString("distributionType", gID_distributionType); -const std::string &gStr_distributionParams = EidosRegisteredString("distributionParams", gID_distributionParams); -const std::string &gStr_dominanceCoeff = EidosRegisteredString("dominanceCoeff", gID_dominanceCoeff); -const std::string &gStr_hemizygousDominanceCoeff = EidosRegisteredString("hemizygousDominanceCoeff", gID_hemizygousDominanceCoeff); +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_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); @@ -1265,8 +1274,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); @@ -1351,6 +1362,11 @@ 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_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); const std::string &gStr_sharedParentCount = EidosRegisteredString("sharedParentCount", gID_sharedParentCount); const std::string &gStr_mutationsOfType = EidosRegisteredString("mutationsOfType", gID_mutationsOfType); @@ -1368,10 +1384,17 @@ 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_setSelectionCoeff = EidosRegisteredString("setSelectionCoeff", gID_setSelectionCoeff); +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_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_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); const std::string &gStr_addPatternForNull = EidosRegisteredString("addPatternForNull", gID_addPatternForNull); @@ -1381,6 +1404,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); @@ -1559,6 +1584,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); @@ -2605,6 +2631,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 ec01b206..92e1aa7a 100644 --- a/core/slim_globals.h +++ b/core/slim_globals.h @@ -124,7 +124,8 @@ 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 +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 @@ -451,8 +452,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; @@ -572,6 +574,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. @@ -762,6 +770,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; @@ -774,6 +783,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; @@ -820,19 +835,22 @@ 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; 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_distributionType; -extern const std::string &gStr_distributionParams; -extern const std::string &gStr_dominanceCoeff; -extern const std::string &gStr_hemizygousDominanceCoeff; +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_hemizygousDominance; extern const std::string &gStr_mutationStackGroup; extern const std::string &gStr_mutationStackPolicy; //extern const std::string &gStr_start; now gEidosStr_start @@ -846,6 +864,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; @@ -871,6 +890,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; @@ -931,6 +951,11 @@ 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_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; extern const std::string &gStr_sharedParentCount; extern const std::string &gStr_mutationsOfType; @@ -948,10 +973,17 @@ 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_setSelectionCoeff; +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_drawSelectionCoefficient; -extern const std::string &gStr_setDistribution; +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; extern const std::string &gStr_addPatternForNull; @@ -961,6 +993,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; @@ -1135,6 +1169,7 @@ extern const std::string &gStr_setBorderless; 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; @@ -1223,6 +1258,7 @@ enum _SLiMGlobalStringID : int { gID_initializeGenomicElementType, gID_initializeMutationType, gID_initializeMutationTypeNuc, + gID_initializeTrait, gID_initializeChromosome, gID_initializeGeneConversion, gID_initializeMutationRate, @@ -1235,6 +1271,11 @@ enum _SLiMGlobalStringID : int { gID_initializeSLiMModelType, gID_initializeInteractionType, + gID_baselineOffset, + gID_individualOffsetMean, + gID_individualOffsetSD, + gID_directFitnessEffect, + gID_genomicElements, gID_lastPosition, gID_hotspotEndPositions, @@ -1281,19 +1322,22 @@ enum _SLiMGlobalStringID : int { gID_mutationFractions, gID_mutationMatrix, gID_isFixed, + gID_isIndependentDominance, + gID_isNeutral, gID_isSegregating, gID_mutationType, gID_nucleotide, gID_nucleotideValue, gID_originTick, gID_position, - gID_selectionCoeff, gID_subpopID, gID_convertToSubstitution, - gID_distributionType, - gID_distributionParams, - gID_dominanceCoeff, - gID_hemizygousDominanceCoeff, + gID_defaultDominanceForTrait, + gID_defaultHemizygousDominanceForTrait, + gID_effectDistributionTypeForTrait, + gID_effectDistributionParamsForTrait, + gID_dominance, + gID_hemizygousDominance, gID_mutationStackGroup, gID_mutationStackPolicy, //gID_start, now gEidosID_start @@ -1307,8 +1351,10 @@ enum _SLiMGlobalStringID : int { gID_allScriptBlocks, gID_allSpecies, gID_allSubpopulations, + gID_allTraits, gID_chromosome, gID_chromosomes, + gID_traits, gID_genomicElementTypes, gID_lifetimeReproductiveOutput, gID_lifetimeReproductiveOutputM, @@ -1392,6 +1438,11 @@ enum _SLiMGlobalStringID : int { gID_positionsOfMutationsOfType, gID_containsMarkerMutation, gID_haplosomesForChromosomes, + gID_offsetForTrait, + gID_phenotypeForTrait, + gID_demandPhenotype, + gID_setOffsetForTrait, + gID_setPhenotypeForTrait, gID_relatedness, gID_sharedParentCount, gID_mutationsOfType, @@ -1409,10 +1460,17 @@ enum _SLiMGlobalStringID : int { gID_setGenomicElementType, gID_setMutationFractions, gID_setMutationMatrix, - gID_setSelectionCoeff, + gID_effectForTrait, + gID_dominanceForTrait, + gID_hemizygousDominanceForTrait, + gID_setEffectForTrait, + gID_setDominanceForTrait, + gID_setHemizygousDominanceForTrait, gID_setMutationType, - gID_drawSelectionCoefficient, - gID_setDistribution, + gID_drawEffectForTrait, + gID_setDefaultDominanceForTrait, + gID_setDefaultHemizygousDominanceForTrait, + gID_setEffectDistributionForTrait, gID_addPatternForClone, gID_addPatternForCross, gID_addPatternForNull, @@ -1421,6 +1479,8 @@ enum _SLiMGlobalStringID : int { gID_chromosomesOfType, gID_chromosomesWithIDs, gID_chromosomesWithSymbols, + gID_traitsWithIndices, + gID_traitsWithNames, gID_addSubpopSplit, gID_estimatedLastTick, gID_deregisterScriptBlock, @@ -1596,6 +1656,7 @@ enum _SLiMGlobalStringID : int { gID_text, gID_title, + gID_Trait, gID_Chromosome, gID_Haplosome, gID_GenomicElement, diff --git a/core/slim_test.cpp b/core/slim_test.cpp index 3f0f9970..d7fa2f3b 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; } @@ -392,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); } "); @@ -461,10 +430,12 @@ int RunSLiMTests(void) _RunChromosomeTests(); _RunMutationTests(); _RunHaplosomeTests(temp_path); + _RunMultitraitTests(); _RunSubpopulationTests(); _RunIndividualTests(); _RunSubstitutionTests(); _RunSLiMEidosBlockTests(); + _RunMateChoiceTests(); _RunContinuousSpaceTests(); _RunSpatialMapTests(); _RunNonWFTests(); diff --git a/core/slim_test.h b/core/slim_test.h index 248e787e..397e5d53 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); @@ -56,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 1bbc800e..a05a4a6b 100644 --- a/core/slim_test_core.cpp +++ b/core/slim_test_core.cpp @@ -50,13 +50,13 @@ void _RunInitTests(void) SLiMAssertScriptRaise("initialize() { initializeMutationType(-1, 0.5, 'f', 0.0); stop(); }", "identifier value is out of range", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType('p2', 0.5, 'f', 0.0); stop(); }", "identifier prefix 'm' was expected", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType('mm1', 0.5, 'f', 0.0); stop(); }", "must be a simple integer", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'f'); stop(); }", "requires exactly 1 DFE parameter", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0, 0.0); stop(); }", "requires exactly 1 DFE parameter", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'g', 0.0); stop(); }", "requires exactly 2 DFE parameters", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'e', 0.0, 0.0); stop(); }", "requires exactly 1 DFE parameter", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'n', 0.0); stop(); }", "requires exactly 2 DFE parameters", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'p', 0.0); stop(); }", "requires exactly 2 DFE parameters", __LINE__); - SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'w', 0.0); stop(); }", "requires exactly 2 DFE parameters", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'f'); stop(); }", "requires exactly 1 DES parameter", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'f', 0.0, 0.0); stop(); }", "requires exactly 1 DES parameter", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'g', 0.0); stop(); }", "requires exactly 2 DES parameters", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'e', 0.0, 0.0); stop(); }", "requires exactly 1 DES parameter", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'n', 0.0); stop(); }", "requires exactly 2 DES parameters", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'p', 0.0); stop(); }", "requires exactly 2 DES parameters", __LINE__); + SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'w', 0.0); stop(); }", "requires exactly 2 DES parameters", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'f', 'foo'); stop(); }", "must be of type numeric", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'g', 'foo', 0.0); stop(); }", "must be of type numeric", __LINE__); SLiMAssertScriptRaise("initialize() { initializeMutationType('m1', 0.5, 'g', 0.0, 'foo'); stop(); }", "must be of type numeric", __LINE__); @@ -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) @@ -2661,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/slim_test_genetics.cpp b/core/slim_test_genetics.cpp index f9d2c63c..2186d069 100644 --- a/core/slim_test_genetics.cpp +++ b/core/slim_test_genetics.cpp @@ -39,9 +39,10 @@ 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.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__); @@ -58,9 +59,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 +68,84 @@ 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); - - // Test MutationType - (float)drawSelectionCoefficient([integer$ n = 1]) + 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__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'n' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'p' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); if (m1.effectDistributionTypeForTrait() == 'w' & identical(m1.effectDistributionParamsForTrait(), c(3.1, 7.5))) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'return 1;'); if (m1.effectDistributionTypeForTrait() == 's' & identical(m1.effectDistributionParamsForTrait(), 'return 1;')) stop(); }", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'x', 1.5); stop(); }", "must be 'f', 'g', 'e', 'n', 'w', or 's'", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 'foo', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 'foo'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 3); stop(); }", "must be of type string", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', '1', 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, '1'); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 3.1); stop(); }", "must be of type string", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', T, 7.5); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, T); stop(); }", "must be of type numeric", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', T); stop(); }", "must be of type string", __LINE__); + + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, -1.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, -1.0); }", "must have a standard deviation parameter >= 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 0.0); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, -1.0); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 0.0, 7.5); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', -1.0, 7.5); }", "must have a scale parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 0.0); }", "must have a shape parameter > 0", __LINE__); + SLiMAssertScriptRaise(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, -7.5); }", "must have a shape parameter > 0", __LINE__); + + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'return foo;'); } 100 early() { stop(); }", "undefined identifier foo", __LINE__, false); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'x >< 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DES callback script", __LINE__, false); + SLiMAssertScriptRaise(gen1_setup_highmut_p1 + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'x $ 5;'); } 100 early() { stop(); }", "tokenize/parse error in type 's' DES callback script", __LINE__, false); + + // Test MutationType - (float)drawEffectForTrait([integer$ n = 1]) // 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.setEffectDistributionForTrait(NULL, 'f', 2.2); if (abs(m1.drawEffectForTrait() - 2.2) < 1e-6) stop(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'f', 2.2); if (all(abs(m1.drawEffectForTrait(NULL, 10) - rep(2.2, 10)) < 1e-6)) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'g', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3.0); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'e', -3.0); if (abs(mean(m1.drawEffectForTrait(NULL, 30000)) + 3.0) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 0.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'n', 3.1, 0.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'p', 3.1, 0.01); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 3.1) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 'w', 3.1, 7.5); if (abs(mean(m1.drawEffectForTrait(NULL, 2000)) - 2.910106) < 0.1) stop(); }", __LINE__); + SLiMAssertScriptSuccess(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); m1.drawEffectForTrait(); }", __LINE__); + SLiMAssertScriptStop(gen1_setup + "1 early() { m1.setEffectDistributionForTrait(NULL, 's', 'rbinom(1, 4, 0.5);'); if (abs(mean(m1.drawEffectForTrait(NULL, 5000)) - 2.0) < 0.1) stop(); }", __LINE__); } #pragma mark GenomicElementType tests @@ -652,12 +656,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 +671,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 @@ -689,13 +687,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 } @@ -744,7 +742,7 @@ void _RunHaplosomeTests(const std::string &temp_path) SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewDrawnMutation(1, 5000, 237); stop(); }", __LINE__); // bad subpop, but this is legal to allow "tagging" of mutations SLiMAssertScriptRaise(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewDrawnMutation(1, 5000, -1); stop(); }", "out of range", __LINE__); // however, such tags must be within range - // Test Haplosome + (object)addNewMutation(io mutationType, numeric selectionCoeff, integer position, [Nio originSubpop]) + // Test Haplosome + (object)addNewMutation(io mutationType, numeric effect, integer position, [Nio originSubpop]) SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewMutation(m1, 0.1, 5000, p1); p1.haplosomes.addMutations(mut); stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewMutation(m1, 0.1, 5000, 1); p1.haplosomes.addMutations(mut); stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { gen = p1.haplosomes[0]; mut = gen.addNewMutation(m1, 0.1, 5000); p1.haplosomes.addMutations(mut); stop(); }", __LINE__); @@ -784,7 +782,7 @@ void _RunHaplosomeTests(const std::string &temp_path) SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewDrawnMutation(1, 5000, 237); stop(); }", __LINE__); // bad subpop, but this is legal to allow "tagging" of mutations SLiMAssertScriptRaise(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewDrawnMutation(1, 5000, -1); stop(); }", "out of range", __LINE__); // however, such tags must be within range - // Test Haplosome + (object)addNewMutation(io mutationType, numeric selectionCoeff, integer position, [io originSubpop]) with new class method non-multiplex behavior + // Test Haplosome + (object)addNewMutation(io mutationType, numeric effect, integer position, [io originSubpop]) with new class method non-multiplex behavior SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewMutation(m1, 0.1, 5000, p1); stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewMutation(m1, 0.1, 5000, 1); stop(); }", __LINE__); SLiMAssertScriptStop(gen1_setup_p1 + "1 early() { p1.haplosomes.addNewMutation(m1, 0.1, 5000); stop(); }", __LINE__); @@ -941,6 +939,329 @@ 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-5); + 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-5); + initializeMutationType("m1", 0.5, "f", 0.0).convertToSubstitution = T; + 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); + + // 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(); }"); + + 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: 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(); }"); + 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() { 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 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.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(); }"); + 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 + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.height, rep(NAN, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { if (!identical(p1.individuals.weight, rep(NAN, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0; if (!identical(p1.individuals.height, rep(10.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 10.0; if (!identical(p1.individuals.weight, rep(10.0, 5))) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.height = 10.0:14; if (!identical(p1.individuals.height, 10.0:14)) stop(); }"); + SLiMAssertScriptSuccess(mt_base_p1 + "1 late() { p1.individuals.weight = 11.0:15; if (!identical(p1.individuals.weight, 11.0:15)) stop(); }"); + + // 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(); }"); + + // 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(); }"); + 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 + "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(); }"); + + // 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 + 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(); }"); + + // 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 + 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(); }"); + } + + // 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/slim_test_other.cpp b/core/slim_test_other.cpp index 0dd58cfc..781b604b 100644 --- a/core/slim_test_other.cpp +++ b/core/slim_test_other.cpp @@ -2574,13 +2574,13 @@ void _RunNucleotideMethodTests(void) SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc(-1, 0.5, 'f', 0.0); stop(); }", "identifier value is out of range", __LINE__); SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('p2', 0.5, 'f', 0.0); stop(); }", "identifier prefix 'm' was expected", __LINE__); SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('mm1', 0.5, 'f', 0.0); stop(); }", "must be a simple integer", __LINE__); - SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'f'); stop(); }", "requires exactly 1 DFE parameter", __LINE__); - SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'f', 0.0, 0.0); stop(); }", "requires exactly 1 DFE parameter", __LINE__); - SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'g', 0.0); stop(); }", "requires exactly 2 DFE parameters", __LINE__); - SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'e', 0.0, 0.0); stop(); }", "requires exactly 1 DFE parameter", __LINE__); - SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'n', 0.0); stop(); }", "requires exactly 2 DFE parameters", __LINE__); - SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'p', 0.0); stop(); }", "requires exactly 2 DFE parameters", __LINE__); - SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'w', 0.0); stop(); }", "requires exactly 2 DFE parameters", __LINE__); + SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'f'); stop(); }", "requires exactly 1 DES parameter", __LINE__); + SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'f', 0.0, 0.0); stop(); }", "requires exactly 1 DES parameter", __LINE__); + SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'g', 0.0); stop(); }", "requires exactly 2 DES parameters", __LINE__); + SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'e', 0.0, 0.0); stop(); }", "requires exactly 1 DES parameter", __LINE__); + SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'n', 0.0); stop(); }", "requires exactly 2 DES parameters", __LINE__); + SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'p', 0.0); stop(); }", "requires exactly 2 DES parameters", __LINE__); + SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'w', 0.0); stop(); }", "requires exactly 2 DES parameters", __LINE__); SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'f', 'foo'); stop(); }", "must be of type numeric", __LINE__); SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'g', 'foo', 0.0); stop(); }", "must be of type numeric", __LINE__); SLiMAssertScriptRaise(nuc_model_start + "initializeMutationTypeNuc('m1', 0.5, 'g', 0.0, 'foo'); stop(); }", "must be of type numeric", __LINE__); @@ -2736,7 +2736,7 @@ void _RunNucleotideMethodTests(void) // nucleotide & nucleotideValue std::string nuc_highmut("initialize() { initializeSLiMOptions(nucleotideBased=T); initializeAncestralNucleotides(randomNucleotides(1e2)); initializeMutationTypeNuc('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0, mmJukesCantor(1e-2)); initializeGenomicElement(g1, 0, 1e2-1); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } "); - std::string nuc_fixmut("initialize() { initializeSLiMOptions(nucleotideBased=T); initializeAncestralNucleotides(randomNucleotides(1e2)); initializeMutationTypeNuc('m1', 0.5, 'f', 0.0); initializeGenomicElementType('g1', m1, 1.0, mmJukesCantor(1e-2)); initializeGenomicElement(g1, 0, 1e2-1); initializeRecombinationRate(1e-8); } 1 early() { sim.addSubpop('p1', 10); } 10 early() { sim.mutations[0].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__); @@ -3019,7 +3019,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__); diff --git a/core/spatial_map.cpp b/core/spatial_map.cpp index ccdeb43c..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 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] = (float)(red1 * color_1_weight + red2 * color_2_weight); p_rgb_ptr[1] = (float)(green1 * color_1_weight + green2 * color_2_weight); @@ -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.cpp b/core/species.cpp index 46090798..cc262422 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,25 @@ 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 pointers to it forget; 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) @@ -514,6 +534,143 @@ 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); +} + +// 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, 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(); + int traits_value_count = traits_value->Count(); + int 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::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(); + + 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) @@ -1167,6 +1324,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()) { @@ -1205,10 +1363,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_effect_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_effect_t dominance_coeff = static_cast(EidosInterpreter::FloatForString(sub, nullptr)); iss >> sub; slim_objectid_t subpop_index = SLiMEidosScript::ExtractIDFromStringWithPrefix(sub, 'p', nullptr); @@ -1236,10 +1394,10 @@ 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... + // 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(); @@ -1247,9 +1405,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, 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); @@ -1260,18 +1418,17 @@ 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_DFE_ - if (selection_coeff != 0.0) + // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_neutral_mutations_ + if (selection_coeff != (slim_effect_t)0.0) { pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DFE_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; } } 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 @@ -1519,8 +1676,9 @@ 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); + // 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); // this is how to add more header tags in future versions //if (file_version >= 9) @@ -1561,8 +1719,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); @@ -1618,7 +1776,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)) || @@ -1918,6 +2076,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(); @@ -1929,8 +2088,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; @@ -1995,10 +2154,10 @@ 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... + // 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(); @@ -2006,9 +2165,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, 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) @@ -2026,11 +2185,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_DFE_ - if (selection_coeff != 0.0) + // all mutations seen here will be added to the simulation somewhere, so check and set pure_neutral_ and all_neutral_mutations_ + if (selection_coeff != (slim_effect_t)0.0) { pure_neutral_ = false; - mutation_type_ptr->all_pure_neutral_DFE_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; } } @@ -2048,7 +2207,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(); @@ -2231,8 +2389,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; @@ -2300,10 +2458,10 @@ 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... + // 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(); @@ -2311,7 +2469,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) @@ -2377,14 +2535,22 @@ 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; 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); + subpop->UpdateFitness(mutationEffect_callbacks, fitnessEffect_callbacks, p_direct_effect_trait_indices, /* p_force_trait_recalculation */ true); } community_.executing_block_type_ = old_executing_block_type; @@ -2438,22 +2604,24 @@ 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) { // 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,10 +2631,11 @@ 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 - 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); @@ -2591,6 +2760,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(); @@ -2644,7 +2817,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->all_neutral_DES_) using_neutral_muttype = true; } } @@ -2674,6 +2847,32 @@ void Species::RunInitializeCallbacks(void) AllocateTreeSequenceTables(); } +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(); + + // first we make a new MutationBlock object for ourselves + 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 + + // 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 @@ -2791,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) @@ -2838,19 +3037,19 @@ 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(); bool mutation_callbacks_present = mutation_callbacks.size(); bool no_active_callbacks = true; - // a type 's' DFE needs to count as an active callback; it could activate other callbacks, + // a type 's' DES needs to count as an active callback; it could activate other callbacks, // and in any case we need EvolveSubpopulation() to take the non-parallel code path - if (type_s_dfes_present_) + if (type_s_DESs_present_) no_active_callbacks = false; // if there are no active callbacks of any type, we can pretend there are no callbacks at all @@ -2950,7 +3149,7 @@ void Species::WF_GenerateOffspring(void) // then evolve each subpop for (std::pair &subpop_pair : population_.subpops_) - population_.EvolveSubpopulation(*subpop_pair.second, mate_choice_callbacks_present, modify_child_callbacks_present, recombination_callbacks_present, mutation_callbacks_present, type_s_dfes_present_); + population_.EvolveSubpopulation(*subpop_pair.second, mate_choice_callbacks_present, modify_child_callbacks_present, recombination_callbacks_present, mutation_callbacks_present, type_s_DESs_present_); } } @@ -2980,10 +3179,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 @@ -3421,7 +3620,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; @@ -4256,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(); @@ -4289,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; */ @@ -7657,7 +7856,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 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 + 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_; @@ -7671,7 +7879,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 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 + 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_; @@ -9676,6 +9890,8 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapmutation_buffer_; + for (auto mut_info_iter : p_mutInfoMap) { slim_mutationid_t mutation_id = mut_info_iter.first; @@ -9710,7 +9926,9 @@ 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; likewise hemizygous dominance + // FIXME MULTITRAIT this code will also now need to handle the independent dominance case, for which NaN should be in the metadata + Substitution *sub = new Substitution(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, mutation_type_ptr->DefaultDominanceForTrait(0) /* metadata.dominance_coeff_ */, metadata.subpop_index_, metadata.origin_tick_, community_.Tick(), metadata.nucleotide_); // FIXME MULTITRAIT population_.treeseq_substitutions_map_.emplace(position, sub); population_.substitutions_.emplace_back(sub); @@ -9721,9 +9939,11 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapNewMutationFromBlock(); - Mutation *new_mut = new (gSLiM_Mutation_Block + new_mut_index) Mutation(mutation_id, mutation_type_ptr, chromosome_index, position, metadata.selection_coeff_, metadata.subpop_index_, metadata.origin_tick_, metadata.nucleotide_); + // 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 p_mutIndexMap[mutation_id] = new_mut_index; @@ -9735,11 +9955,11 @@ void Species::__CreateMutationsFromTabulation(std::unordered_mapall_pure_neutral_DFE_ = false; + mutation_type_ptr->all_neutral_mutations_ = false; } } } diff --git a/core/species.h b/core/species.h index 2b2b08aa..954ed278 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" @@ -50,17 +51,20 @@ extern "C" { } #endif - class Community; class EidosInterpreter; class Individual; +class MutationBlock; class MutationType; class GenomicElementType; 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 { @@ -78,6 +82,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 - @@ -92,7 +99,8 @@ 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! 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 @@ -176,6 +184,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 @@ -202,6 +215,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 @@ -210,9 +238,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 @@ -272,6 +300,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; @@ -300,17 +330,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 @@ -359,15 +389,15 @@ class Species : public EidosDictionaryUnretained bool has_recalculated_fitness_ = false; // set to true when recalculateFitness() is called, so we know fitness values are valid // optimization of the pure neutral case; this is set to false if (a) a non-neutral mutation is added by the user, (b) a genomic element type is configured to use a - // non-neutral mutation type, (c) an already existing mutation type (assumed to be in use) is set to a non-neutral DFE, or (d) a mutation's selection coefficient is + // non-neutral mutation type, (c) an already existing mutation type (assumed to be in use) is set to a non-neutral DES, or (d) a mutation's selection coefficient is // changed to non-neutral. The flag is never set back to true. Importantly, simply defining a non-neutral mutation type does NOT clear this flag; we want sims to be // able to run a neutral burn-in at full speed, only slowing down when the non-neutral mutation type is actually used. BCH 12 January 2018: Also, note that this flag // is unaffected by the fitness_scaling_ properties on Subpopulation and Individual, which are taken into account even when this flag is set. bool pure_neutral_ = true; // optimization flag // this flag tracks whether a type 's' mutation type has ever been seen; we just set it to true if we see one, we never set it back to false again, for simplicity - // this switches to a less optimized case when evolving in WF models, if a type 's' DFE could be present, since that can open up various cans of worms - bool type_s_dfes_present_ = false; // optimization flag + // this switches to a less optimized case when evolving in WF models, if a type 's' DES could be present, since that can open up various cans of worms + bool type_s_DESs_present_ = false; // optimization flag // this counter is incremented when a selection coefficient is changed on any mutation object in the simulation. This is used as a signal to mutation runs that their // cache of non-neutral mutations is invalid (because their counter is not equal to this counter). The caches will be re-validated the next time they are used. Other @@ -375,9 +405,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 @@ -418,18 +445,40 @@ 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_; } + 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 + { + // 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 + + 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 // 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); 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); @@ -481,6 +530,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_; } 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(); } @@ -607,11 +657,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 +688,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 3ff1d5f6..48d9db1c 100644 --- a/core/species_eidos.cpp +++ b/core/species_eidos.cpp @@ -21,11 +21,13 @@ #include "species.h" #include "community.h" +#include "trait.h" #include "haplosome.h" #include "individual.h" #include "subpopulation.h" #include "polymorphism.h" #include "interaction_type.h" +#include "mutation_block.h" #include "log_file.h" #include @@ -527,11 +529,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->all_neutral_DES_) pure_neutral_ = false; - // the mutation type's all_pure_neutral_DFE_ flag is presumably already set - } } EidosValueType mm_type = mutationMatrix_value->Type(); @@ -585,12 +584,17 @@ 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) { #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"); @@ -602,33 +606,55 @@ 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 dfe_type_string = distributionType_value->StringAtIndex_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)) EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeMutationType): " << p_function_name << "() mutation type m" << map_identifier << " already defined." << EidosTerminate(); - // Parse the DFE type and parameters, and do various sanity checks - DFEType dfe_type; - std::vector dfe_parameters; - std::vector dfe_strings; + // Parse the DES type and parameters, and do various sanity checks + DESType DES_type; + std::vector DES_parameters; + std::vector DES_strings; - MutationType::ParseDFEParameters(dfe_type_string, p_arguments.data() + 3, (int)p_arguments.size() - 3, &dfe_type, &dfe_parameters, &dfe_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 - MutationType *new_mutation_type = new MutationType(*this, map_identifier, dominance_coeff, nucleotide_based, dfe_type, dfe_parameters, dfe_strings, num_mutation_type_inits_); + MutationType *new_mutation_type = new MutationType(*this, map_identifier, dominance_coeff, nucleotide_based, DES_type, DES_parameters, DES_strings, num_mutation_type_inits_); #else - MutationType *new_mutation_type = new MutationType(*this, map_identifier, dominance_coeff, nucleotide_based, dfe_type, dfe_parameters, dfe_strings); + MutationType *new_mutation_type = new MutationType(*this, map_identifier, dominance_coeff, nucleotide_based, DES_type, DES_parameters, DES_strings); #endif mutation_types_.emplace(map_identifier, new_mutation_type); community_.mutation_types_changed_ = true; - // keep track of whether we have ever seen a type 's' (scripted) DFE; if so, we switch to a slower case when evolving - if (dfe_type == DFEType::kScript) - type_s_dfes_present_ = true; + // keep track of whether we have ever seen a type 's' (scripted) DES; if so, we switch to a slower case when evolving + if (DES_type == DESType::kScript) + type_s_DESs_present_ = true; // define a new Eidos variable to refer to the new mutation type EidosSymbolTableEntry &symbol_entry = new_mutation_type->SymbolTableEntry(); @@ -647,17 +673,26 @@ EidosValue_SP Species::ExecuteContextFunction_initializeMutationType(const std:: } else { - output_stream << p_function_name << "(" << map_identifier << ", " << dominance_coeff << ", \"" << dfe_type << "\""; + output_stream << p_function_name << "(" << map_identifier << ", " << dominance_coeff; - if (dfe_parameters.size() > 0) + if (defaultDistribution) { - for (double dfe_param : dfe_parameters) - output_stream << ", " << dfe_param; + output_stream << ", NULL"; } else { - for (const std::string &dfe_param : dfe_strings) - output_stream << ", \"" << dfe_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; @@ -1582,6 +1617,327 @@ 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(); + } + 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(); + + // 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); + + if (name.length() == 0) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name be a non-empty string." << EidosTerminate(); + + if (!EidosScript::Eidos_IsIdentifier(name)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that the trait name is a valid Eidos identifier." << EidosTerminate(nullptr); + + 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(); + + 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; + + 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 + slim_effect_t baselineOffset; + + if (baselineOffset_value->Type() == EidosValueType::kValueNULL) + { + baselineOffset = (slim_effect_t)((type == TraitType::kMultiplicative) ? 1.0 : 0.0); + } + else + { + 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 ((type == TraitType::kMultiplicative) && (baselineOffset < (slim_effect_t)0.0)) + baselineOffset = (slim_effect_t)0.0; + + // check that the default distribution is used or not used, in its entirety + if (individualOffsetMean_value->Type() != individualOffsetSD_value->Type()) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires that both individual offset parameters be NULL (to use the default distribution) or be non-NULL (to specify a distribution)." << EidosTerminate(); + + // individualOffsetMean + double individualOffsetMean; + + if (individualOffsetMean_value->Type() == EidosValueType::kValueNULL) + individualOffsetMean = (type == TraitType::kMultiplicative) ? 1.0 : 0.0; + else + individualOffsetMean = individualOffsetMean_value->FloatAtIndex_NOCAST(0, nullptr); + + if (!std::isfinite(individualOffsetMean)) + EIDOS_TERMINATION << "ERROR (Species::ExecuteContextFunction_initializeTrait): initializeTrait() requires individualOffsetMean to be a finite value (not NAN or INF)." << EidosTerminate(); + + // individualOffsetSD + double individualOffsetSD; + + if (individualOffsetSD_value->Type() == EidosValueType::kValueNULL) + individualOffsetSD = 0.0; + else + individualOffsetSD = individualOffsetSD_value->FloatAtIndex_NOCAST(0, nullptr); + + 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_++; + + // 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); + 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 + 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 + { + // ALSO MAINTAIN: 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); + } + } + + { + // 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 + { + // 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)-> + DeclareAcceleratedSet(Individual::SetProperty_Accelerated_TRAIT_VALUE)); + + gSLiM_Individual_Class->AddSignatureForProperty(signature); + } + } + + { + // add a Mutation Effect property that returns the effect size for the trait in a mutation + const EidosPropertySignature *existing_signature = gSLiM_Mutation_Class->SignatureForProperty(traitEffect_stringID); + + 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 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) + { + 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 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) + { + 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 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) + { + 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); + } + } + + { + // 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(); + + output_stream << "initializeTrait(name='" << name << "', type='" << type_string << "'"; + if (baselineOffset != (slim_effect_t)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]) // @@ -1785,6 +2141,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_)); @@ -1863,7 +2229,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); @@ -1964,7 +2330,17 @@ EidosValue_SP Species::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: + { + // 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) + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(trait, gSLiM_Trait_Class)); + return super::GetProperty(p_property_id); + } } } @@ -2022,6 +2398,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: @@ -2625,7 +3003,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) @@ -2652,6 +3030,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) { @@ -2995,7 +3428,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() @@ -3091,7 +3524,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() @@ -3380,7 +3813,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) { @@ -3548,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) { @@ -3567,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; @@ -3768,6 +4207,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 @@ -3940,7 +4380,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; @@ -4260,7 +4700,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 @@ -4293,6 +4733,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); } @@ -4329,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)); @@ -4342,6 +4783,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, "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/subpopulation.cpp b/core/subpopulation.cpp index 72a52790..c50b57b3 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" @@ -131,7 +132,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 @@ -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(); @@ -1130,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(); @@ -1148,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; } } @@ -1178,6 +1173,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(); @@ -1196,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) @@ -1258,8 +1232,11 @@ 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; { // dispose of haplosomes and individuals with our object pools @@ -1374,1038 +1351,383 @@ 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, bool p_force_trait_recalculation) { - 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; + // 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 DFE, or have been made neutral with a "return 1.0;" - // mutationEffect() callback that is active, (b) for the mutation types that use a neutral DFE, 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 pure neutral according to their DFE - for (auto &mut_type_iter : mut_types) - mut_type_iter.second->is_pure_neutral_now_ = mut_type_iter.second->all_pure_neutral_DFE_; + // we cannot override individual cached fitness values; individuals are not all neutral fitness + individual_cached_fitness_OVERRIDE_ = false; - // 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) + // demand phenotypes for all the relevant traits + if (p_direct_effect_trait_indices.size()) { - 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; - } - } +#warning make a new DemandPhenotype() function for whole subpops +#warning need to think about shuffling the order for DemandPhenotype as well! + if (p_force_trait_recalculation) + gSLiM_Individual_Class->DemandPhenotype(&species_, parent_individuals_.data(), (int)parent_individuals_.size(), p_direct_effect_trait_indices); // FIXME MULTITRAIT: pass in p_mutationEffect_callbacks to a per-subpop version of this + 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; + // 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; - } - } - - // 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; + (this->*(_UpdateFitness_TEMPLATED))(p_fitnessEffect_callbacks, p_direct_effect_trait_indices); } - // 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; - - // Set up to draw random females - 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(+: 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 - { -#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 - - 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 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; - } - } - else - { - // 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; - } - - 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_); - - 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; -#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); + 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 males - 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(+: 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 - - fitness *= subpop_fitness_scaling; - parent_individuals_[male_index]->cached_fitness_UNSAFE_ = fitness; - totalMaleFitness += 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 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_ + IndividualTraitInfo *trait_info = ind->trait_info_; - // 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 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; + 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 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); - -#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; - } + // 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 - // 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); + 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 < 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 - { #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(); - } - } - - totalFitness += totalMaleFitness; - - if (model_type_ == SLiMModelType::kModelTypeWF) - { - if (totalMaleFitness <= 0.0) - EIDOS_TERMINATION << "ERROR (Subpopulation::UpdateFitness): total fitness of males 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); - } - } - 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 - + if (f_has_subpop_fitnessScaling) 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; - } + ind->cached_fitness_UNSAFE_ = 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); - + // 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_[individual_index]->cached_unscaled_fitness_ = fitness; + ind->cached_unscaled_fitness_ = 0.0; #endif - - fitness *= subpop_fitness_scaling; - parent_individuals_[individual_index]->cached_fitness_UNSAFE_ = fitness; - totalFitness += 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 - -#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); - + // fitness is 0.0; we refer to it (in-register, presumably) rather than use 0.0 #ifdef SLIMGUI - parent_individuals_[individual_index]->cached_unscaled_fitness_ = fitness; + ind->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) - { - 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); + ind->cached_fitness_UNSAFE_ = fitness; } } - // 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_); + 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(bool p_pure_neutral) +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 the cached_parental_fitness_ and cached_male_fitness_ buffers, and then generates new lookup tables for mate choice. + // 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. - // Reallocate the fitness buffers to be large enough - if (cached_fitness_capacity_ < parent_subpop_size_) + // 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_) { - 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); - } - - cached_fitness_capacity_ = parent_subpop_size_; + mate_choice_weights_->resize_no_initialize(0); + mate_choice_weights_valid_ = false; } - // 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 + // 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 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_); + + 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_) { - 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; - } + double totalMaleFitness = universal_cached_fitness * (parent_subpop_size_ - parent_first_male_index_); + double totalFemaleFitness = universal_cached_fitness * parent_first_male_index_; - 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; - } + 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 { - for (slim_popsize_t i = 0; i < parent_subpop_size_; i++) - cached_parental_fitness_[i] = universal_cached_fitness; + 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); } } else { - // This is the normal case, where cached_fitness_UNSAFE_ has the cached fitness values for each individual + // This is the normal case, where cached_fitness_UNSAFE_ has cached fitness values for each individual. + // 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 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_)) + { + 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_) { + 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_) { @@ -2422,8 +1744,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_); @@ -2438,15 +1760,16 @@ 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_); } } } -double Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, double p_computed_fitness, std::vector &p_mutationEffect_callbacks, Individual *p_individual) +// FIXME MULTITRAIT: should return slim_effect_t so the caller doesn't have to cast +slim_effect_t Subpopulation::ApplyMutationEffectCallbacks(MutationIndex p_mutation, int p_homozygous, slim_effect_t p_effect, std::vector &p_mutationEffect_callbacks, Individual *p_individual) { THREAD_SAFETY_IN_ANY_PARALLEL("Population::ApplyMutationEffectCallbacks(): running Eidos callback"); @@ -2455,7 +1778,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) { @@ -2508,10 +1832,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 @@ -2528,7 +1852,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 { @@ -2538,8 +1862,8 @@ 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_Float local_effect(p_computed_fitness); + EidosValue_Object local_mut(mut_block_ptr + p_mutation, gSLiM_Mutation_Class); + EidosValue_Float local_effect((double)p_effect); // We need to actually execute the script; we start a block here to manage the lifetime of the symbol table { @@ -2577,6 +1901,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) @@ -2596,10 +1921,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 (...) @@ -2618,10 +1943,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"); @@ -2630,8 +1955,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) { @@ -2677,10 +2001,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 @@ -2700,7 +2024,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 { @@ -2729,7 +2053,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); @@ -2744,10 +2068,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 (...) @@ -2773,7 +2097,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; @@ -2789,550 +2113,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); - } - - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - - 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(&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; - - 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_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= mutation->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(&haplosome1_iter, &haplosome1_max, nonneutral_change_counter, nonneutral_regime); - mutrun2->beginend_nonneutral_pointers(&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; - - 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_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= mutation->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; - - 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_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= mutation->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; - - 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_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= mutation->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; - - 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_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= mutation->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; - - 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_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= mutation->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; - - 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_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= mutation->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; - - 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_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= mutation->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); - } - - Mutation *mut_block_ptr = gSLiM_Mutation_Block; - 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(&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); - - 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_); - - if (w <= 0.0) - return 0.0; - } - else - { - w *= mutation->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) { @@ -4211,7 +2991,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); @@ -4502,6 +3282,10 @@ 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 + // 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 Haplosome **haplosomes = individual->haplosomes_; int currentHaplosomeIndex = 0; @@ -4887,6 +3671,10 @@ 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 + // 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 Haplosome **haplosomes = individual->haplosomes_; const int currentHaplosomeIndex = 0; @@ -4970,6 +3758,10 @@ 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 + // 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 Haplosome **haplosomes = individual->haplosomes_; const int currentHaplosomeIndex = 0; @@ -5050,6 +3842,10 @@ 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 + // 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 Haplosome **haplosomes = individual->haplosomes_; int currentHaplosomeIndex = 0; @@ -5249,6 +4045,10 @@ 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 + // 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 Haplosome **haplosomes = individual->haplosomes_; int currentHaplosomeIndex = 0; @@ -5522,6 +4322,10 @@ 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 + // 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 Haplosome **haplosomes = individual->haplosomes_; const int currentHaplosomeIndex = 0; @@ -5605,6 +4409,10 @@ 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 + // 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 Haplosome **haplosomes = individual->haplosomes_; const int currentHaplosomeIndex = 0; @@ -5860,7 +4668,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"); @@ -5907,7 +4715,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 @@ -6065,12 +4873,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; } @@ -6087,9 +4895,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); @@ -6443,7 +5251,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: @@ -6451,8 +5259,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) @@ -6465,8 +5274,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) @@ -6479,8 +5289,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) @@ -6493,8 +5304,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) @@ -6511,15 +5323,16 @@ 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) { 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; @@ -6538,9 +5351,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; @@ -6567,8 +5380,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) { @@ -6586,13 +5400,14 @@ 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); + 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) @@ -6604,9 +5419,9 @@ void Subpopulation::SetProperty_Accelerated_fitnessScaling(EidosObject **p_value 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; @@ -8927,22 +7742,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); } @@ -11461,7 +10288,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); } @@ -12666,7 +11493,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 0b4ace59..773aba0d 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; @@ -89,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 @@ -160,26 +160,39 @@ 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 - // 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() - 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_ + // 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 mate_choice_weights_ itself, lazily, if it is not already present. + double *cached_parental_fitness_ = nullptr; // OWNED POINTER: cached in UpdateWFFitnessBuffers() + 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. // 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_; @@ -194,7 +207,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 @@ -254,7 +267,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()) { @@ -292,6 +305,11 @@ 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 + // Note that we reuse the existing trait_info_ buffer, with the same number of traits + back->_InitializePerTraitInformation(); + return back; } @@ -364,21 +382,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, bool p_force_trait_recalculation); + + 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 @@ -391,7 +402,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 @@ -419,7 +430,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 @@ -427,7 +438,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); @@ -491,14 +502,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 7d913f0b..9cb6290b 100644 --- a/core/substitution.cpp +++ b/core/substitution.cpp @@ -23,6 +23,8 @@ #include "eidos_call_signature.h" #include "eidos_property_signature.h" #include "species.h" +#include "mutation_block.h" +#include "trait.h" #include #include @@ -35,16 +37,131 @@ #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_), 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); // 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(); + MutationTraitInfo *mut_trait_info = mutation_block->TraitInfoForMutation(&p_mutation); + 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_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) : +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... + Species &species = mutation_type_ptr_->species_; + int trait_count = species.TraitCount(); + + 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 == (slim_effect_t)0.0); + is_independent_dominance_ = std::isnan(p_dominance_coeff); + + trait_info_[0].effect_size_ = p_selection_coeff; + trait_info_[0].dominance_coeff_UNSAFE_ = p_dominance_coeff; // can be NAN + trait_info_[0].hemizygous_dominance_coeff_ = mutation_type_ptr_->DefaultHemizygousDominanceForTrait(0); // FIXME MULTITRAIT: needs to be passed in + + for (int 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 + 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_ != (slim_effect_t)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(); } -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) +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 <= (slim_effect_t)-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 @@ -63,8 +180,27 @@ 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_ << " "; + + // 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 << " " << selection_coeff_ << " " << mutation_type_ptr_->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_) @@ -91,8 +227,27 @@ 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_ << " "; + + // 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 << " " << selection_coeff_ << " " << mutation_type_ptr_->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_) @@ -122,7 +277,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) @@ -141,15 +297,102 @@ 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 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_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((double)trait_info_[0].effect_size_)); + 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((double)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. + // 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) + { + slim_effect_t realized_dominance = RealizedDominanceForTrait(traits[0]); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)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 realized_dominance = RealizedDominanceForTrait(traits[trait_index]); + + float_result->push_float_no_check((double)realized_dominance); + } + + 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((double)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((double)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 @@ -197,12 +440,50 @@ EidosValue_SP Substitution::GetProperty(EidosGlobalStringID p_property_id) // all others, including gID_none default: + // Here we implement a special behavior: you can do substitution.Effect, substitution.Dominance, + // and substitution.HemizygousDominance to access a trait's values directly. + // NOTE: This mechanism also needs to be maintained in Species::ExecuteContextFunction_initializeTrait(). + // NOTE: This mechanism also needs to be maintained in SLiMTypeInterpreter::_TypeEvaluate_FunctionCall_Internal(). + Species &species = mutation_type_ptr_->species_; + const std::string &property_string = EidosStringRegistry::StringForGlobalStringID(p_property_id); + + if ((property_string.length() > 6) && Eidos_string_hasSuffix(property_string, "Effect")) + { + 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((double)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((double)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); + Trait *trait = species.TraitFromName(trait_name); + + if (trait) + { + // 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((double)realized_dominance)); + } + } + return super::GetProperty(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) @@ -215,8 +496,39 @@ 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_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) 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) @@ -240,8 +552,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) @@ -258,8 +571,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) @@ -272,8 +586,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) @@ -286,8 +601,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) @@ -300,8 +616,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) @@ -314,8 +631,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) @@ -332,22 +650,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_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_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) @@ -365,32 +670,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)); @@ -417,7 +696,115 @@ EidosValue_SP Substitution::ExecuteInstanceMethod(EidosGlobalStringID p_method_i { switch (p_method_id) { - 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); + } +} + +// ********************* - (float)effectForTrait([Niso 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((double)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((double)effect); + } + + return EidosValue_SP(float_result); + } +} + +// ********************* - (float)dominanceForTrait([Niso 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_; + 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]; + Trait *trait = traits[trait_index]; + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + + return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float((double)realized_dominance)); + } + else + { + EidosValue_Float *float_result = (new (gEidosValuePool->AllocateChunk()) EidosValue_Float())->reserve(trait_indices.size()); + + for (int64_t trait_index : trait_indices) + { + Trait *trait = traits[trait_index]; + slim_effect_t realized_dominance = RealizedDominanceForTrait(trait); + + float_result->push_float_no_check((double)realized_dominance); + } + + return EidosValue_SP(float_result); + } +} + +// ********************* - (float)hemizygousDominanceForTrait([Niso 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((double)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((double)dominance); + } + + return EidosValue_SP(float_result); } } @@ -429,7 +816,7 @@ EidosValue_SP Substitution::ExecuteInstanceMethod(EidosGlobalStringID p_method_i #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 @@ -442,17 +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_selectionCoeff, true, kEidosValueMaskFloat | kEidosValueMaskSingleton))->DeclareAcceleratedGet(Substitution::GetProperty_Accelerated_selectionCoeff)); - 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_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); } @@ -470,6 +861,10 @@ const std::vector *Substitution_Class::Methods(void) c methods = new std::vector(*super::Methods()); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_effectForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_dominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_hemizygousDominanceForTrait, kEidosValueMaskFloat))->AddIntStringObject_ON("trait", gSLiM_Trait_Class, gStaticEidosValueNULL)); + std::sort(methods->begin(), methods->end(), CompareEidosCallSignatures); } diff --git a/core/substitution.h b/core/substitution.h index b8dad5b0..8b9d6288 100644 --- a/core/substitution.h +++ b/core/substitution.h @@ -34,9 +34,28 @@ #include "chromosome.h" #include "eidos_value.h" +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 +// 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. +// 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_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; class Substitution : public EidosDictionaryRetained { @@ -49,23 +68,34 @@ class Substitution : public EidosDictionaryRetained MutationType *mutation_type_ptr_; // mutation type identifier slim_position_t position_; // position - slim_selcoeff_t selection_coeff_; // selection coefficient slim_objectid_t subpop_index_; // subpopulation in which mutation arose slim_tick_t origin_tick_; // tick in which mutation arose slim_tick_t fixation_tick_; // tick in which mutation fixed slim_chromosome_index_t chromosome_index_; // the (uint8_t) index of this mutation's chromosome + + unsigned int is_neutral_ : 1; // all effects are 0.0; see mutation.h + unsigned int is_independent_dominance_ : 1; // configured for "independent dominance"; see mutation.h + 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 + // 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, 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_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); + + 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); - // 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 { } + // 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; @@ -79,18 +109,22 @@ 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); + 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(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_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_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); + 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_mutationType(EidosGlobalStringID p_property_id, EidosObject **p_values, size_t p_values_size); }; class Substitution_Class : public EidosDictionaryRetained_Class diff --git a/core/trait.cpp b/core/trait.cpp new file mode 100644 index 00000000..b19c46eb --- /dev/null +++ b/core/trait.cpp @@ -0,0 +1,314 @@ +// +// 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, 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 < (slim_effect_t)0.0)) + baselineOffset_ = 0.0; + else + baselineOffset_ = p_baselineOffset; + + _RecacheIndividualOffsetDistribution(); +} + +void Trait::_RecacheIndividualOffsetDistribution(void) +{ + // cache for the fast case of an individual-offset SD of 0.0 + if (individualOffsetSD_ == 0.0) + { + individualOffsetFixed_ = true; + + // effects for multiplicative traits clip at 0.0 + slim_effect_t offset = static_cast(individualOffsetMean_); + + if ((type_ == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) + individualOffsetFixedValue_ = 0.0; + else + individualOffsetFixedValue_ = offset; + } + else + { + individualOffsetFixed_ = false; + } +} + +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_ << ">"; +} + +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()); + + slim_effect_t offset = static_cast(gsl_ran_gaussian(rng, individualOffsetSD_) + individualOffsetMean_); + + // effects for multiplicative traits clip at 0.0 + if ((type_ == TraitType::kMultiplicative) && (offset < (slim_effect_t)0.0)) + offset = 0.0; + + return offset; +} + +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; + + // 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) + { + 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((double)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(); + + // 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: + { + 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; + _RecacheIndividualOffsetDistribution(); + 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; + _RecacheIndividualOffsetDistribution(); + 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 - + +Trait_Class *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, 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))); + 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..3b833bc8 --- /dev/null +++ b/core/trait.h @@ -0,0 +1,151 @@ +// +// 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" + + +class Trait_Class; +extern Trait_Class *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 + 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 + 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, 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)) 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 __attribute__((always_inline)) slim_effect_t DrawIndividualOffset(void) const { return (individualOffsetFixed_) ? individualOffsetFixedValue_ : _DrawIndividualOffset(); } + + inline __attribute__((always_inline)) bool HasDirectFitnessEffect(void) const { return directFitnessEffect_; } + + + // + // 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_call_signature.cpp b/eidos/eidos_call_signature.cpp index 568ce287..fede6234 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); @@ -196,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)); } @@ -208,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); } @@ -220,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)); } @@ -232,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); } @@ -242,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)); } @@ -252,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); } @@ -262,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)); } @@ -272,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); 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_Object.cpp b/eidos/eidos_class_Object.cpp index 97f4378d..86954466 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; @@ -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; @@ -710,6 +746,59 @@ 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 dbf29671..0541ce58 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,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_class_TestElement.cpp b/eidos/eidos_class_TestElement.cpp index 5099bf13..c5c3876e 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); @@ -192,7 +194,7 @@ static EidosValue_SP Eidos_Instantiate_EidosTestElement(const std::vector *EidosTestElement_Class::Properties(void) const @@ -336,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 0e01bff1..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: @@ -69,8 +74,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 @@ -96,9 +101,6 @@ class EidosTestElement_Class : public EidosDictionaryRetained_Class // (object<_TestElementNRR>$)_TestNRR(integer$ value) // -extern EidosClass *gEidosTestElementNRR_Class; - - class EidosTestElementNRR : public EidosObject { private: 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_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_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 0d99d5d1..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" @@ -1330,7 +1337,7 @@ enum _EidosGlobalStringID : uint32_t gEidosID_Individual, gEidosID_LastEntry, // IDs added by the Context should start here - gEidosID_LastContextEntry = 540 // 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 diff --git a/eidos/eidos_interpreter.cpp b/eidos/eidos_interpreter.cpp index c444640e..378c305c 100644 --- a/eidos/eidos_interpreter.cpp +++ b/eidos/eidos_interpreter.cpp @@ -1276,7 +1276,18 @@ void EidosInterpreter::_CreateArgumentList(const EidosASTNode *p_node, const Eid if ((p_call_signature->call_name_ == "defineSpatialMap") && (named_arg == "gridSize")) EIDOS_TERMINATION << "ERROR (EidosInterpreter::_ProcessArgumentList): named argument '" << named_arg << "' skipped over required argument '" << p_call_signature->arg_names_[sig_arg_index] << "'." << std::endl << "NOTE: The defineSpatialMap() method was changed in SLiM 3.5, breaking backward compatibility. Please see the manual for guidance on updating your code." << EidosTerminate(nullptr); - EIDOS_TERMINATION << "ERROR (EidosInterpreter::_ProcessArgumentList): named argument '" << named_arg << "' skipped over required argument '" << p_call_signature->arg_names_[sig_arg_index] << "'; all required arguments must be supplied in order." << EidosTerminate(nullptr); + // Special error-handling for evaluate() because its immediate parameter was removed in SLiM 3.5 + if ((p_call_signature->call_name_ == "evaluate") && (named_arg == "immediate")) + EIDOS_TERMINATION << "ERROR (EidosInterpreter::_ProcessArgumentList): named argument '" << named_arg << "' skipped over required argument '" << p_call_signature->arg_names_[sig_arg_index] << "'." << std::endl << "NOTE: The evaluate() method was changed in SLiM 4.0, breaking backward compatibility. Please see the manual for guidance on updating your code." << EidosTerminate(nullptr); + + // Check whether this named argument exists in the call signature, but is skipping over a required argument, or if it doesn't + // match any named argument in the call. If the latter, we emit a more specific error message now. To help with autofixing, + // it marks the position of the argument name as the error position, which is not how most errors here are reported. + for (int sig_check_index = 0; sig_check_index < sig_arg_count; ++sig_check_index) + if (p_call_signature->arg_names_[sig_check_index] == named_arg) + EIDOS_TERMINATION << "ERROR (EidosInterpreter::_ProcessArgumentList): named argument '" << named_arg << "' skipped over required argument '" << p_call_signature->arg_names_[sig_arg_index] << "'; all required arguments must be supplied in order." << EidosTerminate(nullptr); + + EIDOS_TERMINATION << "ERROR (EidosInterpreter::_ProcessArgumentList): unrecognized named argument '" << named_arg << "' to " << p_call_signature->call_name_ << "(); check that the argument name is spelled correctly." << EidosTerminate(named_arg_name_node->token_); } EidosValue_SP default_value = p_call_signature->arg_defaults_[sig_arg_index]; @@ -1374,7 +1385,8 @@ void EidosInterpreter::_CreateArgumentList(const EidosASTNode *p_node, const Eid EIDOS_TERMINATION << "ERROR (EidosInterpreter::_ProcessArgumentList): argument '" << named_arg << "' to " << p_call_signature->call_name_ << "() could not be matched; probably supplied more than once or supplied out of order (note that arguments must be supplied in order)." << EidosTerminate(nullptr); } - EIDOS_TERMINATION << "ERROR (EidosInterpreter::_ProcessArgumentList): unrecognized named argument '" << named_arg << "' to " << p_call_signature->call_name_ << "()." << EidosTerminate(nullptr); + // BCH 11/2/2025: Changing this to highlight the named argument, rather than the call, to help with autofixing + EIDOS_TERMINATION << "ERROR (EidosInterpreter::_ProcessArgumentList): unrecognized named argument '" << named_arg << "' to " << p_call_signature->call_name_ << "(); check that the argument name is spelled correctly." << EidosTerminate(named_arg_name_node->token_); } else { diff --git a/eidos/eidos_property_signature.h b/eidos/eidos_property_signature.h index 52d64fcc..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 @@ -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_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 // ================================ diff --git a/eidos/eidos_symbol_table.cpp b/eidos/eidos_symbol_table.cpp index 54fc75d5..0ed53253 100644 --- a/eidos/eidos_symbol_table.cpp +++ b/eidos/eidos_symbol_table.cpp @@ -885,6 +885,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 @@ -904,6 +906,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 82eca6e8..43b80e9a 100644 --- a/eidos/eidos_symbol_table.h +++ b/eidos/eidos_symbol_table.h @@ -218,8 +218,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_test.cpp b/eidos/eidos_test.cpp index ea05d36e..4022ae27 100644 --- a/eidos/eidos_test.cpp +++ b/eidos/eidos_test.cpp @@ -1862,10 +1862,10 @@ void _RunFunctionDispatchTests(void) EidosAssertScriptRaise("abs(-10, -10);", 0, "too many arguments supplied"); EidosAssertScriptRaise("abs(x=-10, -10);", 0, "too many arguments supplied"); EidosAssertScriptSuccess_I("abs(x=-10);", 10); - EidosAssertScriptRaise("abs(y=-10);", 0, "skipped over required argument"); + EidosAssertScriptRaise("abs(y=-10);", 4, "unrecognized named argument 'y'"); EidosAssertScriptRaise("abs(x=-10, x=-10);", 0, "supplied more than once"); - EidosAssertScriptRaise("abs(x=-10, y=-10);", 0, "unrecognized named argument 'y'"); - EidosAssertScriptRaise("abs(y=-10, x=-10);", 0, "skipped over required argument"); + EidosAssertScriptRaise("abs(x=-10, y=-10);", 11, "unrecognized named argument 'y'"); + EidosAssertScriptRaise("abs(y=-10, x=-10);", 4, "unrecognized named argument 'y'"); EidosAssertScriptSuccess_I("integerDiv(6, 3);", 2); EidosAssertScriptRaise("integerDiv(6, 3, 3);", 0, "too many arguments supplied"); @@ -1894,17 +1894,17 @@ void _RunFunctionDispatchTests(void) EidosAssertScriptSuccess_NULL("c(NULL);"); EidosAssertScriptSuccess_I("c(2);", 2); EidosAssertScriptSuccess_IV("c(1, 2, 3);", {1, 2, 3}); - EidosAssertScriptRaise("c(x=2);", 0, "unrecognized named argument 'x'"); - EidosAssertScriptRaise("c(x=1, 2, 3);", 0, "unrecognized named argument 'x'"); - EidosAssertScriptRaise("c(1, x=2, 3);", 0, "unrecognized named argument 'x'"); - EidosAssertScriptRaise("c(1, 2, x=3);", 0, "unrecognized named argument 'x'"); + EidosAssertScriptRaise("c(x=2);", 2, "unrecognized named argument 'x'"); + EidosAssertScriptRaise("c(x=1, 2, 3);", 2, "unrecognized named argument 'x'"); + EidosAssertScriptRaise("c(1, x=2, 3);", 5, "unrecognized named argument 'x'"); + EidosAssertScriptRaise("c(1, 2, x=3);", 8, "unrecognized named argument 'x'"); EidosAssertScriptSuccess_I("doCall('abs', -10);", 10); EidosAssertScriptSuccess_I("doCall(functionName='abs', -10);", 10); - EidosAssertScriptRaise("doCall(x='abs', -10);", 0, "skipped over required argument"); - EidosAssertScriptRaise("doCall('abs', x=-10);", 0, "unrecognized named argument 'x'"); + EidosAssertScriptRaise("doCall(x='abs', -10);", 7, "unrecognized named argument 'x'"); + EidosAssertScriptRaise("doCall('abs', x=-10);", 14, "unrecognized named argument 'x'"); EidosAssertScriptRaise("doCall('abs', functionName=-10);", 0, "could not be matched"); - EidosAssertScriptRaise("doCall(x='abs');", 0, "skipped over required argument"); + EidosAssertScriptRaise("doCall(x='abs');", 7, "unrecognized named argument 'x'"); EidosAssertScriptRaise("doCall(functionName='abs');", 0, "requires 1 argument(s), but 0 are supplied"); EidosAssertScriptRaise("foobaz();", 0, "unrecognized function name"); 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"); diff --git a/eidos/eidos_test_functions_other.cpp b/eidos/eidos_test_functions_other.cpp index 87779bf1..5f44cf86 100644 --- a/eidos/eidos_test_functions_other.cpp +++ b/eidos/eidos_test_functions_other.cpp @@ -1824,8 +1824,8 @@ void _RunUserDefinedFunctionTests(void) EidosAssertScriptRaise("function (s)foo(i x) { return x; } foo();", 35, "missing required argument 'x'"); EidosAssertScriptRaise("function (s)foo(i x) { return x; } foo(5, 6);", 35, "too many arguments supplied"); EidosAssertScriptRaise("function (s)foo(i x) { return x; } foo(x=5);", 35, "return value cannot be type integer"); - EidosAssertScriptRaise("function (s)foo(i x) { return x; } foo(y=5);", 35, "named argument 'y' skipped over required argument 'x'"); - EidosAssertScriptRaise("function (s)foo(i x) { return x; } foo(x=5, y=5);", 35, "unrecognized named argument 'y'"); + EidosAssertScriptRaise("function (s)foo(i x) { return x; } foo(y=5);", 39, "unrecognized named argument 'y'"); + EidosAssertScriptRaise("function (s)foo(i x) { return x; } foo(x=5, y=5);", 44, "unrecognized named argument 'y'"); // Mutual recursion EidosAssertScriptSuccess_I("function (i)foo(i x) { return x + bar(x); } function (i)bar(i x) { if (x <= 1) return 1; else return foo(x - 1); } foo(5); ", 16); diff --git a/eidos/eidos_test_functions_vector.cpp b/eidos/eidos_test_functions_vector.cpp index f176593f..8522b208 100644 --- a/eidos/eidos_test_functions_vector.cpp +++ b/eidos/eidos_test_functions_vector.cpp @@ -1119,7 +1119,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_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 diff --git a/eidos/eidos_value.cpp b/eidos/eidos_value.cpp index 77602090..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); @@ -2224,7 +2201,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 +2299,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 +2314,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 { diff --git a/eidos/eidos_value.h b/eidos/eidos_value.h index fad37eae..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; @@ -951,7 +958,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 +994,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 @@ -1010,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; @@ -1083,7 +1087,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 +1112,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)